Carga de los datos

Se toma el dataset disponible en: https://www.kaggle.com/competitions/fcen-dm-2024-prediccin-precio-de-propiedades

De la base de datos, se toman los datos de entrenamiento.

A los registros se les aplicó los siguientes filtros:

l1 - Nivel administrativo 1: (Argentina).

l2 - Nivel administrativo 2: (Capital Federal).

property_type - Tipo de propiedad: (PH).

Operation_type - Tipo de operación: (Venta).

Carga del archivo Excel

dataset <- read_excel("datos_tp1.xlsx")
nrow(dataset)
## [1] 1261

Selección de la muestra

Para la base de datos seleccionada se generó una muestra aleatoria de tamaño n = 500 utilizando como semilla los últimos tres dígitos del DNI (893).

set.seed(893)
datos_orig <- dataset %>%
  sample_n(size=500,replace=FALSE)

Graficación de variables cuantitativas y preprocesamiento de los datos

Se incluye la sección con gráficos de variables cuantitativas puesto que estos gráficos fueron el disparador de un preprocesamiento de los datos. Es decir que se eligieron rangos razonables para las variables estudiadas, y se descartaron del análisis los datos que se encontraran por fuera.

Concretamente, se toman los datos en los cuales la latitud sea menor a -34 grados, y la superficie total sea menor a 5000 metros cuadrados. Con estos filtros, se ve que los puntos quedan contenidos dentro de una región geográfica que podría compararse con la circunscripta en los límites de la Ciudad Autónoma de Buenos Aires. (Aclaración: los valores de latitud y longitud se intercambian porque estaban intercambiados y mal asignados los nombres de las columnas en el dataset original.)

Luego se intercambiaron los valores de superficie total y superficie cubierta, cuando la cubierta era mayor que la total. Se imputaron los valores faltantes de superficie con su contrapartida correspondiente, es decir, cuando faltaba la superficie total se imputó con la superficie cubierta y viceversa. Aquí cabe hacer la salvedad de que la superficie cubierta se imputó con la mediana de la proporción de la superficie cubierta sobre la total, multiplicada por la superficie total. Si se empleaba directamente la total, la aproximación resultaba demasiado grosera, puesto que resultaba en valores excesivamente altos de superficie cubierta.

Luego se imputó las habitaciones (bedrooms) con la cantidad de ambientes (rooms) menos uno. Se imputó los baños con el valor 1 en caso de que faltara el valor. Se realizan otras imputaciones a fin de completar los campos rooms, bedrooms y bathrooms en unos pocos registros.

Por último, se quitó la primera entrada de la muestra de datos por ser un “Edificio de departamentos tipo PH”, y tratarse más bien de un conjunto de siete PHs, y por lo tanto no encajar estrictamente en la categoría propuesta para el análisis, y contrastar excesivamente con el resto de los datos de la muestra. El incluir esta entrada distorsionaba los resultados de los análisis.

Asimismo, se elimina un registro duplicado.

Se muestran los histogramas correspondientes a la cantidad de ambientes, habitaciones y baños, y de precio de las propiedades, junto con un gráfico de cajas del precio. Los histogramas muestran perfiles razonables, y si bien se observan valores extremos compatibles con outliers, al observar los datos concretos vemos que son reales, y que encajan en el subconjunto del dataset original que define la población estudiada y, por lo tanto, no los excluimos del análisis.

datos <- datos_orig
datos <- distinct(datos)

#Swapeamos los datos de latitud y longitud.
datos$tmp <- datos$lon
datos$lon <- datos$lat
datos$lat <- datos$tmp
datos$tmp <- NULL

#datos_filtrados_geo <- datos[datos$lat <= -34, ]
datos_filtrados_geo <- datos %>% 
  filter(lat <= -34)
plot(datos_filtrados_geo$lon, datos_filtrados_geo$lat, xlab = "Longitud", ylab = "Latitud")

#datos_filtro_superficie <- datos_filtrados_geo[datos_filtrados_geo$surface_total <= 5000, ]
datos_filtro_superficie <- datos_filtrados_geo %>% 
  filter(surface_total <= 5000)

cubierta_supera_total <- !is.na(datos_filtro_superficie$surface_covered) & !is.na(datos_filtro_superficie$surface_total) & datos_filtro_superficie$surface_covered > datos_filtro_superficie$surface_total
datos_filtro_superficie[cubierta_supera_total, c("surface_covered", "surface_total")] <- datos_filtro_superficie[cubierta_supera_total, c("surface_total", "surface_covered")]


#Chequeamos que los outliers de precio sean datos verosímiles
datos_filtro_superficie[datos_filtro_superficie$price>=4e5, ]
#Más preprocesamiento de los datos:

sup_no_nula <- !is.na(datos_filtro_superficie$surface_total) & !is.na(datos_filtro_superficie$surface_covered) & (datos_filtro_superficie$surface_total != 0) & (datos_filtro_superficie$surface_covered != 0)
proporcion_sup_cubierta <- datos_filtro_superficie[sup_no_nula, "surface_covered"]/datos_filtro_superficie[sup_no_nula,  "surface_total"]

datos_filtro_superficie$surface_covered[is.na(datos_filtro_superficie$surface_covered)] <- datos_filtro_superficie$surface_total[is.na(datos_filtro_superficie$surface_covered)]*median(proporcion_sup_cubierta$surface_covered)

datos_filtro_superficie$surface_total[is.na(datos_filtro_superficie$surface_total)] <- datos_filtro_superficie$surface_covered[is.na(datos_filtro_superficie$surface_total)]

plot(datos_filtro_superficie$surface_covered ~ datos_filtro_superficie$surface_total, xlab = "Superficie total", ylab = "Superficie cubierta")

datos_filtro_superficie$bathrooms[is.na(datos_filtro_superficie$bathrooms)] <- 1
condicion = (is.na(datos_filtro_superficie$bedrooms)) & !is.na(datos_filtro_superficie$rooms) & (datos_filtro_superficie$rooms>1)
datos_filtro_superficie$bedrooms[condicion] <- datos_filtro_superficie$rooms[condicion] - 1

#null en cantidad de ambientes
rooms_nulo = is.na(datos_filtro_superficie$rooms) & !is.na(datos_filtro_superficie$bedrooms)
datos_filtro_superficie$rooms[rooms_nulo] <- datos_filtro_superficie$bedrooms[rooms_nulo] + 1

rooms_nulo_banio_no_nulo = is.na(datos_filtro_superficie$rooms) & !is.na(datos_filtro_superficie$bathrooms)
#monoambiente
rooms_nulo_banio_unico = rooms_nulo_banio_no_nulo & (datos_filtro_superficie$bathrooms == 1)
datos_filtro_superficie$rooms[rooms_nulo_banio_unico] <- 1
rooms_nulo_banio_unico_bedrooms_nulo = rooms_nulo_banio_unico & is.na(datos_filtro_superficie$bedrooms)
datos_filtro_superficie$bedrooms[rooms_nulo_banio_unico_bedrooms_nulo] <- 1

rooms_nulo_mas_de_un_banio = rooms_nulo_banio_no_nulo & (datos_filtro_superficie$bathrooms > 1)
datos_filtro_superficie$rooms[rooms_nulo_mas_de_un_banio] <- datos_filtro_superficie$bathrooms[rooms_nulo_mas_de_un_banio] + 1

#null en cantidad de habitaciones
bedrooms_nulo_banio_unico = !is.na(datos_filtro_superficie$rooms) & is.na(datos_filtro_superficie$bedrooms) & (datos_filtro_superficie$bathrooms == 1) & (datos_filtro_superficie$rooms == 1)
bedrooms_nulo_mas_de_un_banio = !is.na(datos_filtro_superficie$rooms) & is.na(datos_filtro_superficie$bedrooms) & (datos_filtro_superficie$bathrooms > 1) & (datos_filtro_superficie$rooms > 1)
#monoambiente
datos_filtro_superficie$bedrooms[bedrooms_nulo_banio_unico] <- 1
datos_filtro_superficie$bedrooms[bedrooms_nulo_mas_de_un_banio] <- datos_filtro_superficie$rooms[bedrooms_nulo_mas_de_un_banio] - 1


datos <- datos_filtro_superficie

#Sacamos la primera entrada de la base por ser un "Edificio de departamentos tipo PH" que es más bien un conjunto de 7 PHs
datos[1,]
datos <- datos[-1, ]

#se elimina un dato duplicado
datos[c(243,304),]
datos <-datos[-304, ]

hist(datos$price, main = "", xlab = "Precio", ylab = "Frecuencia")

boxplot(datos$price, main = "Precio")

hist(datos$rooms, main = "", xlab = "Ambientes", ylab = "Frecuencia")

hist(datos$bedrooms, main = "", xlab = "Habitaciones", ylab = "Frecuencia")

hist(datos$bathrooms, main = "", xlab = "Baños", ylab = "Frecuencia")

Análisis exploratorio y descriptivo

Se realiza un análisis exploratorio y descriptivo (EDA) de cada una de las variables cuantitativas. Se presenta la información en la siguiente tabla, conteniendo las siguientes medidas descriptivas:

Cantidad de datos, mínimo, máximo, media, mediana, moda, varianza, desviación estándar, coeficiente de variación, cuartil 1, cuartil 3, rango intercuartílico, MAD, asimetría, curtosis.

vars_cuanti <- c("lat", "lon", "rooms", "bedrooms", "bathrooms", "surface_total", "surface_covered", "price")

datos_cuanti <- datos[, vars_cuanti]

resumen_cuanti <- resumen(datos_cuanti)

resumen_cuanti

Tabla de frecuencias y porcentajes de las variables cualitativas

A continuación se presenta una tabla de frecuencias y porcentaje para las variables cualitativas (categóricas). En la primera tabla se consignan la cantidad de registros en cuya descripción figuró alguna de las palabras clave enumeradas y el porcentaje que representan respecto del total. Las palabras clave con mayor frecuencia fueron: expensas, terraza y escalera.

En la segunda tabla se indica qué cantidad y porcentaje de propiedades se hallaron para cada barrio de Capital Federal. Se puede apreciar que los barrios mejor representados son Palermo, Villa Crespo y Almagro.

tabla_frec <- tabla_frec_dummies(datos, columnas_dummies)
tabla_frec
frec_barrios <- table(datos$l3)
total_barrios <- sum(!is.na(datos$l3))
porcentaje_barrios <- 100*frec_barrios/total_barrios
frec_df <- as.data.frame(frec_barrios)
porcentaje_df <- as.data.frame(porcentaje_barrios)
tabla_barrios <- cbind(frec_df, porcentaje_df[[2]])
colnames(tabla_barrios) <- c("Barrio","Frecuencia", "Porcentaje")

tabla_barrios[order(-tabla_barrios[,2]),]

Graficación de las variables cualitativas

A continuación se presentan dos gráficos que representan la cantidad de registros en cuya descripción figuró alguna de las palabras clave presentadas en la tabla del punto anterior. Se muestra frecuencia absoluta y porcentaje del total respectivamente.

En concordancia con la segunda tabla presentada en el punto anterior se realizó un gráfico de tortas, con los porcentajes de propiedades hallados en los principales barrios de Capital Federal.

ggplot(tabla_frec,aes(x=factor(Variable),y=Frecuencia))+
  geom_col(color='black',fill='cyan3')+
  xlab('')

ggplot(tabla_frec,aes(x=factor(Variable),y=Porcentaje))+
  geom_col(color='black',fill='cyan3')+
  xlab('')

porcentajes <- prop.table(frec_barrios) * 100
porcentajes_ordenados <- sort(porcentajes, decreasing = TRUE)
nro_categorias = 12
categorias_top <- names(porcentajes_ordenados)[1:nro_categorias]
otros_porcentaje <- sum(porcentajes_ordenados[7:length(porcentajes_ordenados)])
nueva_tabla <- c(porcentajes_ordenados[1:nro_categorias], "Otros barrios" = otros_porcentaje)
pie(nueva_tabla, labels = paste(names(nueva_tabla), ": ", round(nueva_tabla, 2), "%"), main = "PH por barrio")

Clasificación jerárquica

A continuación se realizó un análisis de clustering teniendo en cuenta las siguientes variables cuantitativas:

Latitud, Longitud, Número de ambientes, Superficie total, Superficie cubierta y Precio.

No se incluyeron las variables Cantidad de habitaciones y Cantidad de baños por ser variables correlacionadas entre sí y con número de ambientes. Las variables se estandarizaron previamente al análisis.

Se tomó la distancia euclídea para elaborar la matriz de distancias o disimilitud. Como algoritmo de ligamiento se empleó el promedio (average linkage) por presentar el mayor índice de correlación cofenética, respecto de las otras alternativas: el completo, el simple, y Ward.

Se utilizó el método del codo (elbow) para fijar el número de clusters. A partir del gráfico de sumas de cuadrados dentro de los clusters en función del número de clusters, se tomó el punto donde se observaba una disminución significativa de dicha magnitud, resultando en seis grupos.

A continuación, se graficó el dendrograma correspondiente, y según con el criterio elegido, se realizó el corte en seis grupos. En este dendrograma ya se puede apreciar que existe un grupo mayoritario (379 observaciones), luego un grupo minoritario (23 observaciones), y luego grupos conformados por pocas observaciones, o sólo una observación.

Esto se observa mejor cuando se grafican los grupos en los ejes del PCA, donde se detecta que el grupo mayoritario está integrado por propiedades de menor precio, superficie y cantidad de ambientes. El grupo que le sigue en número se asocia a valores más altos de dichas variables.

No es tan significativo el análisis minucioso de los grupos de unas pocas observaciones que se forman, ya que estos se distinguen por la conjunción de características particulares que los separan de los clusters más grandes: precio muy elevado, superficie cubierta muy alta (puede ser un efecto de la imputación que realizamos en nuestro preprocesamiento de los datos).

En este sentido, se analizaron los resultados de realizar un clustering jerárquico, con el método de ligamiento promedio, y fijando un k = 2 (no se muestran los resultados). Se vio que los grupos formados no eran los esperables, quedando unas pocas observaciones extremas separadas del resto.

Cabe señalar que estando las variables estandarizadas, e incluyéndose dos variables de superficie (total y cubierta), es como si, conceptualmente, la superficie pesara el doble que el resto de las variables). Este punto y el tener variables correlacionadas como el número de ambientes, la superficie y el precio, pueden afectar los resultados del clustering.

vars_cuanti_cluster <- c("lat", "lon", "rooms", "surface_total", "surface_covered", "price")

datos_cuanti_cluster <- na.omit(datos[, vars_cuanti_cluster])

datos_cuanti_cluster_std <- scale(datos_cuanti_cluster)
mat_dist <- dist(x = datos_cuanti_cluster_std, method = "euclidean") 

hc_complete <- hclust(d = mat_dist, method = "complete") 
hc_average <- hclust(d = mat_dist, method = "average")
hc_single <- hclust(d = mat_dist, method = "single")
hc_ward <- hclust(d = mat_dist, method = "ward.D2")
cor(x = mat_dist, cophenetic(hc_complete))
## [1] 0.7048128
cor(x = mat_dist, cophenetic(hc_average))
## [1] 0.8255693
cor(x = mat_dist, cophenetic(hc_single))
## [1] 0.7983609
cor(x = mat_dist, cophenetic(hc_ward))
## [1] 0.522708
#average linkage es la que resultó mejor, seguimos con esa
library(factoextra)
fviz_nbclust(x = datos_cuanti_cluster_std, FUNcluster = hcut,hc_method ="average",stand=TRUE, method = "wss", diss = dist(datos_cuanti_cluster_std, method = "euclidean"))

#el coeficiente de Silhouette da 2 grupos
#fviz_nbclust(x = datos_cuanti_cluster_std, FUNcluster = hcut,hc_method ="average",stand=TRUE, method = "silhouette", diss = dist(datos_cuanti_cluster_std, method = "euclidean"))
#el estadístico gap da 1 grupo
#fviz_nbclust(x = datos_cuanti_cluster_std, FUNcluster = hcut,hc_method ="average",stand=TRUE, method = "gap", diss = dist(datos_cuanti_cluster_std, method = "euclidean"))
grupos = 6
plot(hc_average, labels = FALSE)
clusters = cutree(hc_average, k = grupos)
rect.hclust(hc_average, k=grupos, border="red")

fviz_cluster(object = list(data = datos_cuanti_cluster_std, cluster = cutree(hc_average, k = grupos)), ellipse.type = "convex", repel = TRUE, show.clust.cent = FALSE) + theme_bw()

cluster_pca <- prcomp(datos_cuanti_cluster_std)
fviz_pca_biplot(cluster_pca, label = "var")

#Hay PHs individuales que se separan por sus características, pero básicamente se distinguen dos grandes grupos
#Grupos minoritarios: detalle de los registros
datos_cuanti_cluster[c(393,261,364), ]
datos_cuanti_cluster[which(clusters == 3), ]
datos[c(393,261,364), ]
datos[which(clusters == 3), ]
#Cantidades de los grupos mayoritarios:
sum(clusters == 1)
## [1] 379
sum(clusters == 2)
## [1] 23
#Medidas de resumen de los clusters mayoritarios
resumen(datos_cuanti[which(clusters == 1),])
resumen(datos_cuanti[which(clusters == 2),])

Para complementar el análisis anterior, utilizamos además el método de ligamiento Ward o de varianza mínima para el clustering jerárquico. El gráfico de la suma de cuadrados dentro en función del número de grupos, no es de interpretación tan inmediata como en el caso del ligamiento promedio puesto que hay un descenso abrupto para k=2, pero sigue disminuyendo considerablemente hasta k=5. Se elige k=5 (el método de Silhouette arroja un resultado similar, por eso no lo se lo incluye).

El análisis más inmediato del dendrograma resultante permite apreciar que se formaron grupos más homogéneos, en consonancia con las características del método de ligamiento elegido. Si se observa cómo quedaron agrupados los puntos en los ejes del PCA, se verá que este agrupamiento es similar al arrojado por el método de k-means (y la interpretación de los grupos también será similar). Asimismo, un punto a destacar es que los grupos también tienen una cantidad de observaciones más homogénea entre ellos y, a diferencia del método de ligamiento promedio, no nos arroja grupos con una, dos, o unas pocas observaciones.

También se muestran los resultados de la corrida con k = 2. En los ejes del PCA, se puede apreciar que se forman dos grupos con un número considerable de observaciones, que se condicen con distintos rangos de precio, superficie total y cubierta, y cantidad de ambientes.

#Ward

fviz_nbclust(x = datos_cuanti_cluster_std, FUNcluster = hcut,hc_method ="ward",stand=TRUE, method = "wss", diss = dist(datos_cuanti_cluster_std, method = "euclidean"))

#el coeficiente de Silhouette da 2 grupos
#fviz_nbclust(x = datos_cuanti_cluster_std, FUNcluster = hcut,hc_method ="ward",stand=TRUE, method = "silhouette", diss = dist(datos_cuanti_cluster_std, method = "euclidean"))

grupos_ward = 5
plot(hc_ward, labels = FALSE)
clusters_ward = cutree(hc_ward, k = grupos_ward)
rect.hclust(hc_ward, k=grupos_ward, border="red")

fviz_cluster(object = list(data = datos_cuanti_cluster_std, cluster = cutree(hc_ward, k = grupos_ward)), ellipse.type = "convex", repel = TRUE, show.clust.cent = FALSE) + theme_bw()

for (i in 1:grupos_ward) {
  print(paste("Observaciones en el grupo", i, ":", sum(clusters_ward == i)))
}
## [1] "Observaciones en el grupo 1 : 87"
## [1] "Observaciones en el grupo 2 : 149"
## [1] "Observaciones en el grupo 3 : 85"
## [1] "Observaciones en el grupo 4 : 18"
## [1] "Observaciones en el grupo 5 : 68"
grupos_reducido = 2
plot(hc_ward, labels = FALSE)
clusters_reducido = cutree(hc_ward, k = grupos_reducido)
rect.hclust(hc_ward, k=grupos_reducido, border="red")

fviz_cluster(object = list(data = datos_cuanti_cluster_std, cluster = cutree(hc_ward, k = grupos_reducido)), ellipse.type = "convex", repel = TRUE, show.clust.cent = FALSE) + theme_bw()

fviz_pca_biplot(cluster_pca, label = "var")

for (i in 1:grupos_reducido) {
  print(paste("Observaciones en el grupo", i, ":", sum(clusters_reducido == i)))
}
## [1] "Observaciones en el grupo 1 : 105"
## [1] "Observaciones en el grupo 2 : 302"

K-means

Para elegir la cantidad óptima de grupos, el valor de k, se emplea nuevamente el método del codo por considerar que éste es el que brinda más información en nuestro caso particular de aplicación. La suma de cuadrados dentro parece reducirse en forma más drástica hasta k=5, y luego se estabiliza. Es por esto que realizamos el análisis de las k-medias con un k fijado en cinco grupos.

De graficar el resultado del análisis en los primeros dos ejes del PCA, se puede concluir lo siguiente. En primer lugar, el método ha formado tres grupos más a la izquierda, con precio y superficie más bajos, que se distinguen entre ellos por la latitud, es decir por la ubicación geográfica. Seguidamente, se tiene el grupo 1, con valores de precio y superficie intermedios, y por último al grupo 5 que reúne las propiedades con valores más extremos de precio y superficie. Este último grupo resulta en un agrupamiento un tanto artificial, porque como podemos apreciar en el biplot, la distancia al centroide es muy variable entre las observaciones del grupo, y en algunos casos comparativamente grande.

Cabe senalar que este método ha arrojado grupos considerablemente más equilibrados en cuanto a la cantidad de observaciones que contienen que el método de clustering jerárquico con método de ligamiento promedio. A esto se puede agregar el hecho de que la diferencia no puede provenir de los datos, ya que se emplearon las mismas variables (estandarizadas) para ambos análisis. La diferencia observada proviene del método de k-means, que se basa en calcular distancias a centroides, y que tiende a conformar grupos esféricos.

fviz_nbclust(x = datos_cuanti_cluster_std, FUNcluster = kmeans, method = "wss", 
             diss = dist(datos_cuanti_cluster_std, method = "euclidean")) #+   geom_vline(xintercept = 5, linetype = 2)

#El coeficiente de Silhouette da 2 grupos
#fviz_nbclust(x = datos_cuanti_cluster_std, FUNcluster = kmeans, method = "silhouette", 
#             diss = dist(datos_cuanti_cluster_std, method = "euclidean"))
#El estadístico gap da 1 grupo
#fviz_nbclust(x = datos_cuanti_cluster_std, FUNcluster = kmeans, method = "gap_stat", 
#             diss = dist(datos_cuanti_cluster_std, method = "euclidean"))

set.seed(893)
km_clusters <- kmeans(x = datos_cuanti_cluster_std, centers = 5, nstart = 25)
fviz_cluster(object = km_clusters, data = datos_cuanti_cluster_std, show.clust.cent = TRUE, ellipse.type = "euclid", star.plot = TRUE, repel = TRUE) + 
  theme_bw() # + theme(legend.position = "none")

cluster_pca <- prcomp(datos_cuanti_cluster_std)
fviz_pca_biplot(cluster_pca, label = "var")

km_clusters$size
## [1]  71  94  20 130  92

PCA

A continuación se realizó un análisis de componentes principales teniendo en cuenta las siguientes variables cuantitativas:

Latitud, Longitud, Número de ambientes, Cantidad de baños, Cantidad de habitaciones, Superficie total, Superficie cubierta y Precio.

Las variables se estandarizaron previamente al análisis. En primer lugar, del gráfico de correlaciones se puede extraer que salvo por la ubicación geográfica, el resto de las variables se encuentran correlacionadas positivamente, es decir, en forma directa, entre sí.

Si se analiza el porcentaje de la varianza explicada a medida que se agregan componentes principales, se puede detectar que con los primeros dos ejes se explica aproximadamente un 70% de la varianza, lo cual se considera suficiente. Al mismo tiempo, este criterio subjetivo es compatible con el resultado arrojado por el Scree-plot, si se sigue el criterio de Kaiser, donde se aprecia que los primeros dos ejes presentan autovalores mayores a 1.

Del biplot correpondiente a mostrar las variables y las observaciones en los dos primeros ejes del PCA, se puede concluir que hay dos tipos de variables principales: las primeras son proxy del tamaño de la propiedad: número de ambientes, habitaciones, baños, superficie total y cubierta. El segundo eje está correlacionado fuertemente con la latitud, y el tercero, que no se muestra en gráficamente pero sí en la tabla que se muestra al final de la sección, donde se encuentran los loadings o cargas, está fuertemente correlacionado con la longitud. Por esta razón podríamos afirmar que existe un segundo grupo de dos variables que son la ubicación geográfica. El precio está más correlacionado (y en forma directa) con el primer grupo de variables que con el segundo.

Este resultado es lógico dado que el PCA detecta correlaciones, a lo cual subyace la idea de linealidad, y el precio está ligado, en términos geográficos, a los distintos barrios, lo cual sigue una lógica diferente a la lineal con la latitud y la longitud.

vars_cuanti_pca <- c("lat", "lon", "rooms", "bathrooms", "bedrooms", "surface_total", "surface_covered", "price")

datos_cuanti_pca <- na.omit(datos[, vars_cuanti_pca])

library(corrplot)

m_cor <- cor(datos_cuanti_pca)
corrplot(m_cor,
         method="circle",
         type = "upper",
         diag= FALSE) 

datos_cuanti_pca_std <- data.frame(scale(datos_cuanti_pca))

pca <- prcomp(datos_cuanti_pca, scale = TRUE)

prop_varianza <- pca$sdev^2 / sum(pca$sdev^2)
prop_varianza_acum <- cumsum(prop_varianza)
round(prop_varianza_acum*100,2)
## [1]  56.17  70.28  82.47  90.48  95.66  97.96  99.63 100.00
screeplot(pca, type = "l", npcs = 8)
abline(h = 1, col="red", lty=5)
legend("topright", legend=c("autovalor = 1"),
       col=c("red"), lty=5, cex=0.6)

library(ggfortify)

autoplot(pca, 
         data = datos_cuanti_pca, 
         loadings = TRUE, 
         loadings.colour = 'black',
         loadings.label = TRUE, 
         loadings.label.size = 5)

library(kableExtra)
round(pca$rotation,2) |> knitr::kable(format = "html") |> 
  kable_styling()
PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8
lat 0.00 0.88 -0.17 -0.34 -0.08 -0.25 0.04 -0.04
lon 0.10 -0.18 -0.97 0.04 0.13 -0.07 -0.05 0.00
rooms 0.41 -0.21 0.05 -0.51 -0.12 0.05 -0.12 -0.71
bathrooms 0.37 0.05 -0.03 0.44 -0.77 -0.20 -0.17 0.02
bedrooms 0.41 -0.15 0.04 -0.53 -0.09 0.05 -0.10 0.71
surface_total 0.40 0.07 0.18 0.25 0.55 -0.47 -0.46 0.00
surface_covered 0.44 -0.02 0.05 0.13 0.15 -0.19 0.85 -0.01
price 0.39 0.34 -0.04 0.26 0.17 0.79 -0.09 -0.01

PCA robusto

Para el PCA robusto se empleó el método del Elipsoide de Volumen Mínimo (MVE). La principal diferencia que se observa entre el PCA clásico y el robusto es que en este último, los puntos se encuentran menos aglomerados en el biplot. Para poder visualizar esta diferencia más claramente, se realizó un clustering jerárquico con los mismos parámetros que el mostrado anteriormente, solo que se utilizaron las variables del PCA (se incluyó cantidad de baños y de habitaciones) y se fijó como criterio de corte el formar dos grupos. Estos dos grupos son similares o análogos a los dos grupos más grandes que se observaban en el biplot que mostraba los resultados del clustering jerárquico, en la sección correspondiente (utilizando el método de ligamiento Ward).

En los biplots del PCA clásico y robusto se puede observar como este último separa mejor los puntos de uno de los dos grupos, aparecen como más “elongados” a lo largo del primer componente. Como se puede observar en el gráfico que compara las varianzas explicadas por los componentes principales, para el PCA clásico (no robusto) y para el robusto (MVE), emplear el método robusto implicó para este caso de estudio, una pérdida de la proporción de la varianza explicada por los primeros ejes.

Como se puede apreciar en los biplots, en el PCA robusto las variables también aparecen más separadas entre sí, y esto se puede apreciar en los loadings, donde vemos que las correlaciones de las variables originales con los ejes no son tan extremas (muy próximas a 1 o -1, o a 0) y están repartidas más “equitativamente” (se observan coeficientes de correlación intermedios). Otro punto para destacar es que la latitud y la longitud parecen más ortogonales en el biplot del PCA clásico que en el PCA robusto, donde se encuentran prácticamente alineadas.

set.seed(893)
pca_mve <-princomp(datos_cuanti_pca, 
                   cor=TRUE, 
                   scores=TRUE,
                   covmat=MASS::cov.mve(datos_cuanti_pca)) #empleamos MVE con datos estandarizados

screeplot(pca_mve, type = "l", npcs = 7)
abline(h = 1, col="red", lty=5)
legend("topright", legend=c("Autovalor = 1"),
col=c("red"), lty=5, cex=0.6)

autoplot(pca_mve, 
         data = datos_cuanti_pca_std, 
         loadings = TRUE, 
         loadings.colour = 'black',
         loadings.label = TRUE, 
         loadings.label.size = 5)

mat_dist_pca <- dist(x = datos_cuanti_pca_std, method = "euclidean") 
hc_ward_pca <- hclust(d = mat_dist_pca, method = "ward.D2")
clusters_pca = cutree(hc_ward_pca, k = 2)

theme <- theme(text = element_text(size=10),
               plot.title = element_text(size=12, face="bold.italic",
               hjust = 0.5),
               axis.title.x = element_text(size=10, face="bold", colour='black'),
               axis.title.y = element_text(size=10, face="bold"),
               panel.border = element_blank(),
               panel.grid.major = element_blank(),
               panel.grid.minor = element_blank(), 
               legend.title = element_text(face="bold"))

library(ggbiplot)
#Comparación de biplots
#Biplot del PCA clásico
ggbiplot(pca, obs.scale=0.1 ,var.scale=1,alpha=0.5
         ,groups=factor(clusters_pca)) + theme

#Biplot del PCA robusto (MVE)
ggbiplot(pca_mve, obs.scale=0.1 ,var.scale=1,alpha=0.5
         ,groups=factor(clusters_pca)) + theme

library(ggpubr)

par(mfrow=c(2,1))
p1 <-fviz_eig(pca_mve, ncp =5, addlabels = TRUE, main="MVE")
p2<- fviz_eig(pca, ncp =5, addlabels = TRUE, main="No robusto")
ggarrange(p1,p2, nrow = 1, ncol = 2)

pca_mve$loadings
## 
## Loadings:
##                 Comp.1 Comp.2 Comp.3 Comp.4 Comp.5 Comp.6 Comp.7 Comp.8
## lat                     0.438  0.738  0.367  0.150  0.266  0.165       
## lon                    -0.541  0.565 -0.519 -0.294  0.179              
## rooms            0.414 -0.401         0.290  0.269                0.707
## bathrooms        0.297  0.294        -0.646  0.620  0.118              
## bedrooms         0.414 -0.401         0.290  0.269               -0.707
## surface_total    0.404  0.215 -0.290        -0.479  0.681 -0.116       
## surface_covered  0.464                      -0.177 -0.321  0.795       
## price            0.427  0.240  0.211        -0.319 -0.558 -0.550       
## 
##                Comp.1 Comp.2 Comp.3 Comp.4 Comp.5 Comp.6 Comp.7 Comp.8
## SS loadings     1.000  1.000  1.000  1.000  1.000  1.000  1.000  1.000
## Proportion Var  0.125  0.125  0.125  0.125  0.125  0.125  0.125  0.125
## Cumulative Var  0.125  0.250  0.375  0.500  0.625  0.750  0.875  1.000

Análisis de correspondencias simple

A fin de realizar el análisis de correspondencias, se discretizaron las siguiente variables cuantitativas:

Precio, Latitud, Longitud, Superficie total, Superficie cubierta y Número de ambientes.

En cada caso se eligieron tres categorías, y los datos se repartieron en forma uniforme en cada una de las categorías. Es decir, cada categoría contiene 1/3 de los datos.

Para el análisis de correpondencias simple se emplearon las variables, así discretizadas: Precio y Superficie total. Como en la tabla de frecuencias esperadas correspondiente a la tabla de contingencia se detectaron celdas con frecuencias menores a 5, se fusionaron las categorías media y alta de ambas variables, para poder realizar el test de independencia de Chi-cuadrado.

Con la tabla así construída, con dos variables ahora dicotómicas, se realizó el test de independencia de Chi-cuadarado. El test resulta significativo (p<0.05), es decir que las variables son dependientes. Este resultado no es sorprendente si se observan los gráficos de Precio según Superficie total, o si se observan los gráficos de perfiles (para los datos subdivididos en tres categorías por variable).

Puesto que el test de independencia fue significativo, se abre paso al Análisis de Correspondencias Simple (CA), para el cual empleamos los datos subdivididos en tres categorías por variable. Como es de esperar, se puede apreciar que el primer componente absorbe la mayor parte de la inercia total, y de hecho, a lo largo de este eje vemos que se van ordenando las observaciones con precio bajo y superficie total baja en un extremo, en el medio las de precio medio y superficie total media, y en extremo opuesto los valores altos de ambas variables.

Si se observa la contribución de filas y de columnas a la inercia total, podemos concluir que las categorías que más contribuyen a la falta de independencia son Precio Alto y Superficie total Alta.

library(FactoMineR)

datos_discretizados <- data.frame(
rango_precio <- cut(datos$price, breaks = 3, labels = c("Bajo", "Medio", "Alto")),
rango_lon <- cut(datos$lon, breaks = 3, labels = c("Oeste", "Centro", "Este")),
rango_lat <- cut(datos$lat, breaks = 3, labels = c("Sur", "Centro", "Norte")),
rango_sup_cub <- cut(datos$surface_covered, breaks = 3, labels = c("Baja", "Media", "Alta")),
rango_sup_tot <- cut(datos$surface_total, breaks = 3, labels = c("Baja", "Media", "Alta")),
rango_cant_amb <- cut(datos$rooms, breaks = 3, labels = c("Baja", "Media", "Alta"))
)
colnames(datos_discretizados) = c("Precio","Lon", "Lat", "Sup_cubierta", "Sup_total", "Cant_amb")
datos_discretizados = na.omit(datos_discretizados)

tabla_frecuencias = table(datos_discretizados$Precio, datos_discretizados$Sup_total)


dimnames(tabla_frecuencias)<-list(Precio=c("Bajo","Medio","Alto"),Sup_total=c("Baja","Media","Alta"))
#rownames(tabla_frecuencias) <-  paste("Precio_", rownames(tabla_frecuencias), sep = "")
#colnames(tabla_frecuencias) <-  paste("Sup_total_", colnames(tabla_frecuencias), sep = "")
df_frecuencias = as.data.frame.table(tabla_frecuencias)
df_frecuencias_exp <- df_frecuencias[rep(1:nrow(df_frecuencias), df_frecuencias[,3]),-3]


categoria_precio_medio_alto <- ifelse(df_frecuencias_exp$Precio %in% c("Medio", "Alto"), "MedioAlto", "Bajo")
categoria_sup_total_media_alta <- ifelse(df_frecuencias_exp$Sup_total %in% c("Media", "Alta"), "MediaAlta", "Baja")
tabla_frecuencias_fusionadas = table(categoria_precio_medio_alto,categoria_sup_total_media_alta)
dimnames(tabla_frecuencias_fusionadas)<-list(Precio=c("Bajo","Medio-Alto"),Sup_total=c("Baja","Media-Alta"))

tabla_frecuencias
##        Sup_total
## Precio  Baja Media Alta
##   Bajo   337     7    0
##   Medio   41    16    2
##   Alto     0     2    2
chisq.test(tabla_frecuencias)$expected
##        Sup_total
## Precio        Baja      Media       Alta
##   Bajo  319.488943 21.1302211 3.38083538
##   Medio  54.796069  3.6240786 0.57985258
##   Alto    3.714988  0.2457002 0.03931204
tabla_frecuencias_fusionadas
##             Sup_total
## Precio       Baja Media-Alta
##   Bajo        337          7
##   Medio-Alto   41         22
chisq.test(tabla_frecuencias_fusionadas)
## 
##  Pearson's Chi-squared test with Yates' continuity correction
## 
## data:  tabla_frecuencias_fusionadas
## X-squared = 82.122, df = 1, p-value < 2.2e-16
library(gplots)
balloonplot(t(as.table(as.matrix(tabla_frecuencias_fusionadas))), main ="Precio según Superficie total", xlab ="", ylab="",
            label = FALSE, show.margins = FALSE)

par(bg="lightcyan")
barplot(tabla_frecuencias_fusionadas,beside=TRUE,col= c("aquamarine3","tan1"),ylim=c(0,280),ylab="Cantidad de propiedades")
mtext("Precio según la Superficie total",cex=1,line=1)
legend("topright",cex=0.8,title="Precio",c("Bajo","Medio-Alto"), fill=c("aquamarine3","tan1"),horiz=F, box.lty = 0)

prop.table(tabla_frecuencias_fusionadas)
##             Sup_total
## Precio             Baja Media-Alta
##   Bajo       0.82800983 0.01719902
##   Medio-Alto 0.10073710 0.05405405
ggplot(data=df_frecuencias_exp, aes(x = Precio, fill= Sup_total))+geom_bar(position='fill', alpha=0.9)+
  labs(title = 'Distribución de la categoría superficie total según precio',
              y = 'Frecuencia de propiedades', x = 'Precio') +
  scale_fill_viridis_d(name='Superficie total') 

precio_sup.ac = CA(tabla_frecuencias,graph=FALSE)

# Contribución de filas y columnas al eje 1
fviz_contrib(precio_sup.ac,choice="row",axes=1, fill="royalblue",color ="black") + labs(title = 'Contribución de filas',  x = 'Precio', y = 'Contribución (%)')

fviz_contrib(precio_sup.ac,choice="col",axes=1, fill="royalblue",color ="black") + labs(title = 'Contribución de columnas',  x = 'Superficie', y = 'Contribución (%)')

fviz_ca_biplot( precio_sup.ac , repel  =TRUE, col.row="royalblue",col.col="indianred") + labs(title='Biplot Análisis de correspondencias Simple')

Análisis de correspondencias múltiple

Para el análisis de correspondencias múltiple (MCA), se emplearon todas las variables que se habían categorizado en el punto anterior.

Como se puede apreciar en el Scree-plot correspondiente, el primer eje es el que más absorbe de la inercia total, y luego la contribución es pareja. Elegimos dos ejes para poder visualizar los puntos en dos dimensiones. Emplear más dimensiones, o sea mostrar los biplots para las distintas combinaciones de ejes complicaría el análisis y la interpretación.

En el biplot de los dos primeros ejes se puede apreciar cómo las observaciones que tienen precio alto también tienen superficie total y cubierta alta, y estas observaciones son comparativamente pocas, en comparación con las restantes. De hecho la mayor parte de las observaciones se concentran en los valores bajos de las variables, y la nube de puntos se va elongando y dispersando hacia los valores medios de precio y de superficie total y cubierta.

Algunas de las observaciones que más aportan a la inercia total, que se separan del resto y están asociadas a valores altos de las variables, ya habían sido identificadas en el clustering jerárquico realizado con el método de ligamiento promedio.

datos.mca <- MCA(datos_discretizados, graph = FALSE)
#Loadings
datos.mca$var$coord
##                           Dim 1       Dim 2       Dim 3       Dim 4       Dim 5
## Bajo               -0.285766616  0.09954735  0.11394744  0.06424683 -0.07956603
## Medio               1.298502678 -0.90654594 -0.65263040 -0.64783448  0.62988179
## Alto                5.423014454  4.81048070 -0.17318144  4.03033092 -2.44807789
## Lon_Oeste          -0.430431328  0.13608369  0.51493834  0.52869100  0.72316288
## Lon_Centro          0.193564044 -0.13542293 -0.75766381  0.11342944 -0.25801995
## Lon_Este            0.243692837  0.09084794  0.89844434 -1.09366068 -0.56139135
## Lat_Sur            -0.130615605  0.01862568  1.46283402  0.63243757  0.89927037
## Lat_Centro          0.034313177  0.05413149  0.04178909 -0.42692113 -0.52965951
## Lat_Norte           0.005959508 -0.14810688 -1.12974922  0.62174389  0.69100224
## Sup_cubierta_Baja  -0.265621409  0.06550331 -0.02098334 -0.01060957 -0.01885261
## Sup_cubierta_Media  2.435891441 -1.46301798  0.19840932  0.36397568 -0.05954347
## Sup_cubierta_Alta   4.473875958  6.01084262  0.30408185 -2.02141458  2.23510002
## Sup_total_Baja     -0.228048223  0.01898162 -0.04250666 -0.01768700 -0.02235074
## Sup_total_Media     2.728404014 -1.47178777  0.88806489  0.32089636  0.18899348
## Sup_total_Alta      4.498031942  7.40491074 -1.53352614 -0.33418061  0.93093576
## Cant_amb_Baja      -0.265731915  0.02921577 -0.05232874  0.03608562 -0.04615493
## Cant_amb_Media      2.026509997 -0.20053997  0.31327835 -0.71500037  0.71902383
## Cant_amb_Alta       4.527511610 -0.97644532  2.73601690  8.84101096 -7.10497050
fviz_screeplot(datos.mca, addlabels = TRUE)

fviz_mca_biplot( datos.mca , repel  =TRUE, col.ind="cos2",invisible="quali")

LS0tCnRpdGxlOiAiVHJhYmFqbyBQcsOhY3RpY28gMSIKYXV0aG9yOiAiQWd1c3TDrW4gSGVycmVyYSIKb3V0cHV0OgogICBodG1sX2RvY3VtZW50OgogICAgIHRvYzogeWVzCiAgICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICAgdG9jX2Zsb2F0OiB5ZXMKICAgICBkZl9wcmludDogcGFnZWQKICAgICB0aGVtZTogdW5pdGVkCiAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQoKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlID0gRkFMU0UpCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KG1vbWVudHMpCmxpYnJhcnkoZ2dwbG90MikKYGBgCiMgQ2FyZ2EgZGUgbG9zIGRhdG9zCgpTZSB0b21hIGVsIGRhdGFzZXQgZGlzcG9uaWJsZSBlbjogaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jb21wZXRpdGlvbnMvZmNlbi1kbS0yMDI0LXByZWRpY2Npbi1wcmVjaW8tZGUtcHJvcGllZGFkZXMKCkRlIGxhIGJhc2UgZGUgZGF0b3MsIHNlIHRvbWFuIGxvcyBkYXRvcyBkZSBlbnRyZW5hbWllbnRvLgoKQSBsb3MgcmVnaXN0cm9zIHNlIGxlcyBhcGxpY8OzIGxvcyBzaWd1aWVudGVzIGZpbHRyb3M6CgpsMSAtIE5pdmVsIGFkbWluaXN0cmF0aXZvIDE6IChBcmdlbnRpbmEpLgoKbDIgLSBOaXZlbCBhZG1pbmlzdHJhdGl2byAyOiAoQ2FwaXRhbCBGZWRlcmFsKS4KCnByb3BlcnR5X3R5cGUgLSBUaXBvIGRlIHByb3BpZWRhZDogKFBIKS4KCk9wZXJhdGlvbl90eXBlIC0gVGlwbyBkZSBvcGVyYWNpw7NuOiAoVmVudGEpLgoKIyMgQ2FyZ2EgZGVsIGFyY2hpdm8gRXhjZWwKCmBgYHtyIGxlY3R1cmFfZGF0b3N9CgpkYXRhc2V0IDwtIHJlYWRfZXhjZWwoImRhdG9zX3RwMS54bHN4IikKbnJvdyhkYXRhc2V0KQpgYGAKCiMjIFNlbGVjY2nDs24gZGUgbGEgbXVlc3RyYQoKUGFyYSBsYSBiYXNlIGRlIGRhdG9zIHNlbGVjY2lvbmFkYSBzZSBnZW5lcsOzIHVuYSBtdWVzdHJhIGFsZWF0b3JpYSBkZSB0YW1hw7FvIG4gPSA1MDAgdXRpbGl6YW5kbyBjb21vIHNlbWlsbGEgbG9zIMO6bHRpbW9zIHRyZXMgZMOtZ2l0b3MgZGVsIEROSSAoODkzKS4KCmBgYHtyIHNlbGVjY2lvbl9tdWVzdHJhfQoKc2V0LnNlZWQoODkzKQpkYXRvc19vcmlnIDwtIGRhdGFzZXQgJT4lCiAgc2FtcGxlX24oc2l6ZT01MDAscmVwbGFjZT1GQUxTRSkKYGBgCgojIEdyYWZpY2FjacOzbiBkZSB2YXJpYWJsZXMgY3VhbnRpdGF0aXZhcyB5IHByZXByb2Nlc2FtaWVudG8gZGUgbG9zIGRhdG9zClNlIGluY2x1eWUgbGEgc2VjY2nDs24gY29uIGdyw6FmaWNvcyBkZSB2YXJpYWJsZXMgY3VhbnRpdGF0aXZhcyBwdWVzdG8gcXVlIGVzdG9zIGdyw6FmaWNvcyBmdWVyb24gZWwgZGlzcGFyYWRvciBkZSB1biBwcmVwcm9jZXNhbWllbnRvIGRlIGxvcyBkYXRvcy4gRXMgZGVjaXIgcXVlIHNlIGVsaWdpZXJvbiByYW5nb3MgcmF6b25hYmxlcyBwYXJhIGxhcyB2YXJpYWJsZXMgZXN0dWRpYWRhcywgeSBzZSBkZXNjYXJ0YXJvbiBkZWwgYW7DoWxpc2lzIGxvcyBkYXRvcyBxdWUgc2UgZW5jb250cmFyYW4gcG9yIGZ1ZXJhLgoKQ29uY3JldGFtZW50ZSwgc2UgdG9tYW4gbG9zIGRhdG9zIGVuIGxvcyBjdWFsZXMgbGEgbGF0aXR1ZCBzZWEgbWVub3IgYSAtMzQgZ3JhZG9zLCB5IGxhIHN1cGVyZmljaWUgdG90YWwgc2VhIG1lbm9yIGEgNTAwMCBtZXRyb3MgY3VhZHJhZG9zLiBDb24gZXN0b3MgZmlsdHJvcywgc2UgdmUgcXVlIGxvcyBwdW50b3MgcXVlZGFuIGNvbnRlbmlkb3MgZGVudHJvIGRlIHVuYSByZWdpw7NuIGdlb2dyw6FmaWNhIHF1ZSBwb2Ryw61hIGNvbXBhcmFyc2UgY29uIGxhIGNpcmN1bnNjcmlwdGEgZW4gbG9zIGzDrW1pdGVzIGRlIGxhIENpdWRhZCBBdXTDs25vbWEgZGUgQnVlbm9zIEFpcmVzLiAoQWNsYXJhY2nDs246IGxvcyB2YWxvcmVzIGRlIGxhdGl0dWQgeSBsb25naXR1ZCBzZSBpbnRlcmNhbWJpYW4gcG9ycXVlIGVzdGFiYW4gaW50ZXJjYW1iaWFkb3MgeSBtYWwgYXNpZ25hZG9zIGxvcyBub21icmVzIGRlIGxhcyBjb2x1bW5hcyBlbiBlbCBkYXRhc2V0IG9yaWdpbmFsLikKCkx1ZWdvIHNlIGludGVyY2FtYmlhcm9uIGxvcyB2YWxvcmVzIGRlIHN1cGVyZmljaWUgdG90YWwgeSBzdXBlcmZpY2llIGN1YmllcnRhLCBjdWFuZG8gbGEgY3ViaWVydGEgZXJhIG1heW9yIHF1ZSBsYSB0b3RhbC4KU2UgaW1wdXRhcm9uIGxvcyB2YWxvcmVzIGZhbHRhbnRlcyBkZSBzdXBlcmZpY2llIGNvbiBzdSBjb250cmFwYXJ0aWRhIGNvcnJlc3BvbmRpZW50ZSwgZXMgZGVjaXIsIGN1YW5kbyBmYWx0YWJhIGxhIHN1cGVyZmljaWUgdG90YWwgc2UgaW1wdXTDsyBjb24gbGEgc3VwZXJmaWNpZSBjdWJpZXJ0YSB5IHZpY2V2ZXJzYS4gQXF1w60gY2FiZSBoYWNlciBsYSBzYWx2ZWRhZCBkZSBxdWUgbGEgc3VwZXJmaWNpZSBjdWJpZXJ0YSBzZSBpbXB1dMOzIGNvbiBsYSBtZWRpYW5hIGRlIGxhIHByb3BvcmNpw7NuIGRlIGxhIHN1cGVyZmljaWUgY3ViaWVydGEgc29icmUgbGEgdG90YWwsIG11bHRpcGxpY2FkYSBwb3IgbGEgc3VwZXJmaWNpZSB0b3RhbC4gU2kgc2UgZW1wbGVhYmEgZGlyZWN0YW1lbnRlIGxhIHRvdGFsLCBsYSBhcHJveGltYWNpw7NuIHJlc3VsdGFiYSBkZW1hc2lhZG8gZ3Jvc2VyYSwgcHVlc3RvIHF1ZSByZXN1bHRhYmEgZW4gdmFsb3JlcyBleGNlc2l2YW1lbnRlIGFsdG9zIGRlIHN1cGVyZmljaWUgY3ViaWVydGEuCgpMdWVnbyBzZSBpbXB1dMOzIGxhcyBoYWJpdGFjaW9uZXMgKGJlZHJvb21zKSBjb24gbGEgY2FudGlkYWQgZGUgYW1iaWVudGVzIChyb29tcykgbWVub3MgdW5vLiBTZSBpbXB1dMOzIGxvcyBiYcOxb3MgY29uIGVsIHZhbG9yIDEgZW4gY2FzbyBkZSBxdWUgZmFsdGFyYSBlbCB2YWxvci4gU2UgcmVhbGl6YW4gb3RyYXMgaW1wdXRhY2lvbmVzIGEgZmluIGRlIGNvbXBsZXRhciBsb3MgY2FtcG9zIHJvb21zLCBiZWRyb29tcyB5IGJhdGhyb29tcyBlbiB1bm9zIHBvY29zIHJlZ2lzdHJvcy4KClBvciDDumx0aW1vLCBzZSBxdWl0w7MgbGEgcHJpbWVyYSBlbnRyYWRhIGRlIGxhIG11ZXN0cmEgZGUgZGF0b3MgcG9yIHNlciB1biAiRWRpZmljaW8gZGUgZGVwYXJ0YW1lbnRvcyB0aXBvIFBIIiwgeSB0cmF0YXJzZSBtw6FzIGJpZW4gZGUgdW4gY29uanVudG8gZGUgc2lldGUgUEhzLCB5IHBvciBsbyB0YW50byBubyBlbmNhamFyIGVzdHJpY3RhbWVudGUgZW4gbGEgY2F0ZWdvcsOtYSBwcm9wdWVzdGEgcGFyYSBlbCBhbsOhbGlzaXMsIHkgY29udHJhc3RhciBleGNlc2l2YW1lbnRlIGNvbiBlbCByZXN0byBkZSBsb3MgZGF0b3MgZGUgbGEgbXVlc3RyYS4gRWwgaW5jbHVpciBlc3RhIGVudHJhZGEgZGlzdG9yc2lvbmFiYSBsb3MgcmVzdWx0YWRvcyBkZSBsb3MgYW7DoWxpc2lzLgoKQXNpbWlzbW8sIHNlIGVsaW1pbmEgdW4gcmVnaXN0cm8gZHVwbGljYWRvLgoKU2UgbXVlc3RyYW4gbG9zIGhpc3RvZ3JhbWFzIGNvcnJlc3BvbmRpZW50ZXMgYSBsYSBjYW50aWRhZCBkZSBhbWJpZW50ZXMsIGhhYml0YWNpb25lcyB5IGJhw7FvcywgeSBkZSBwcmVjaW8gZGUgbGFzIHByb3BpZWRhZGVzLCBqdW50byBjb24gdW4gZ3LDoWZpY28gZGUgY2FqYXMgZGVsIHByZWNpby4gTG9zIGhpc3RvZ3JhbWFzIG11ZXN0cmFuIHBlcmZpbGVzIHJhem9uYWJsZXMsIHkgc2kgYmllbiBzZSBvYnNlcnZhbiB2YWxvcmVzIGV4dHJlbW9zIGNvbXBhdGlibGVzIGNvbiBvdXRsaWVycywgYWwgb2JzZXJ2YXIgbG9zIGRhdG9zIGNvbmNyZXRvcyB2ZW1vcyBxdWUgc29uIHJlYWxlcywgeSBxdWUgZW5jYWphbiBlbiBlbCBzdWJjb25qdW50byBkZWwgZGF0YXNldCBvcmlnaW5hbCBxdWUgZGVmaW5lIGxhIHBvYmxhY2nDs24gZXN0dWRpYWRhIHksIHBvciBsbyB0YW50bywgbm8gbG9zIGV4Y2x1aW1vcyBkZWwgYW7DoWxpc2lzLgoKYGBge3IgZ3JhZmljb3NfY3VhbnRpdGF0aXZhc30KCmRhdG9zIDwtIGRhdG9zX29yaWcKZGF0b3MgPC0gZGlzdGluY3QoZGF0b3MpCgojU3dhcGVhbW9zIGxvcyBkYXRvcyBkZSBsYXRpdHVkIHkgbG9uZ2l0dWQuCmRhdG9zJHRtcCA8LSBkYXRvcyRsb24KZGF0b3MkbG9uIDwtIGRhdG9zJGxhdApkYXRvcyRsYXQgPC0gZGF0b3MkdG1wCmRhdG9zJHRtcCA8LSBOVUxMCgojZGF0b3NfZmlsdHJhZG9zX2dlbyA8LSBkYXRvc1tkYXRvcyRsYXQgPD0gLTM0LCBdCmRhdG9zX2ZpbHRyYWRvc19nZW8gPC0gZGF0b3MgJT4lIAogIGZpbHRlcihsYXQgPD0gLTM0KQpwbG90KGRhdG9zX2ZpbHRyYWRvc19nZW8kbG9uLCBkYXRvc19maWx0cmFkb3NfZ2VvJGxhdCwgeGxhYiA9ICJMb25naXR1ZCIsIHlsYWIgPSAiTGF0aXR1ZCIpCiNkYXRvc19maWx0cm9fc3VwZXJmaWNpZSA8LSBkYXRvc19maWx0cmFkb3NfZ2VvW2RhdG9zX2ZpbHRyYWRvc19nZW8kc3VyZmFjZV90b3RhbCA8PSA1MDAwLCBdCmRhdG9zX2ZpbHRyb19zdXBlcmZpY2llIDwtIGRhdG9zX2ZpbHRyYWRvc19nZW8gJT4lIAogIGZpbHRlcihzdXJmYWNlX3RvdGFsIDw9IDUwMDApCgpjdWJpZXJ0YV9zdXBlcmFfdG90YWwgPC0gIWlzLm5hKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHN1cmZhY2VfY292ZXJlZCkgJiAhaXMubmEoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkc3VyZmFjZV90b3RhbCkgJiBkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRzdXJmYWNlX2NvdmVyZWQgPiBkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRzdXJmYWNlX3RvdGFsCmRhdG9zX2ZpbHRyb19zdXBlcmZpY2llW2N1YmllcnRhX3N1cGVyYV90b3RhbCwgYygic3VyZmFjZV9jb3ZlcmVkIiwgInN1cmZhY2VfdG90YWwiKV0gPC0gZGF0b3NfZmlsdHJvX3N1cGVyZmljaWVbY3ViaWVydGFfc3VwZXJhX3RvdGFsLCBjKCJzdXJmYWNlX3RvdGFsIiwgInN1cmZhY2VfY292ZXJlZCIpXQoKCiNDaGVxdWVhbW9zIHF1ZSBsb3Mgb3V0bGllcnMgZGUgcHJlY2lvIHNlYW4gZGF0b3MgdmVyb3PDrW1pbGVzCmRhdG9zX2ZpbHRyb19zdXBlcmZpY2llW2RhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHByaWNlPj00ZTUsIF0KCiNNw6FzIHByZXByb2Nlc2FtaWVudG8gZGUgbG9zIGRhdG9zOgoKc3VwX25vX251bGEgPC0gIWlzLm5hKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHN1cmZhY2VfdG90YWwpICYgIWlzLm5hKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHN1cmZhY2VfY292ZXJlZCkgJiAoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkc3VyZmFjZV90b3RhbCAhPSAwKSAmIChkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRzdXJmYWNlX2NvdmVyZWQgIT0gMCkKcHJvcG9yY2lvbl9zdXBfY3ViaWVydGEgPC0gZGF0b3NfZmlsdHJvX3N1cGVyZmljaWVbc3VwX25vX251bGEsICJzdXJmYWNlX2NvdmVyZWQiXS9kYXRvc19maWx0cm9fc3VwZXJmaWNpZVtzdXBfbm9fbnVsYSwgICJzdXJmYWNlX3RvdGFsIl0KCmRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHN1cmZhY2VfY292ZXJlZFtpcy5uYShkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRzdXJmYWNlX2NvdmVyZWQpXSA8LSBkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRzdXJmYWNlX3RvdGFsW2lzLm5hKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHN1cmZhY2VfY292ZXJlZCldKm1lZGlhbihwcm9wb3JjaW9uX3N1cF9jdWJpZXJ0YSRzdXJmYWNlX2NvdmVyZWQpCgpkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRzdXJmYWNlX3RvdGFsW2lzLm5hKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHN1cmZhY2VfdG90YWwpXSA8LSBkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRzdXJmYWNlX2NvdmVyZWRbaXMubmEoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkc3VyZmFjZV90b3RhbCldCgpwbG90KGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHN1cmZhY2VfY292ZXJlZCB+IGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHN1cmZhY2VfdG90YWwsIHhsYWIgPSAiU3VwZXJmaWNpZSB0b3RhbCIsIHlsYWIgPSAiU3VwZXJmaWNpZSBjdWJpZXJ0YSIpCgpkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRiYXRocm9vbXNbaXMubmEoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkYmF0aHJvb21zKV0gPC0gMQpjb25kaWNpb24gPSAoaXMubmEoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkYmVkcm9vbXMpKSAmICFpcy5uYShkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRyb29tcykgJiAoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkcm9vbXM+MSkKZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkYmVkcm9vbXNbY29uZGljaW9uXSA8LSBkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRyb29tc1tjb25kaWNpb25dIC0gMQoKI251bGwgZW4gY2FudGlkYWQgZGUgYW1iaWVudGVzCnJvb21zX251bG8gPSBpcy5uYShkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRyb29tcykgJiAhaXMubmEoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkYmVkcm9vbXMpCmRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHJvb21zW3Jvb21zX251bG9dIDwtIGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJGJlZHJvb21zW3Jvb21zX251bG9dICsgMQoKcm9vbXNfbnVsb19iYW5pb19ub19udWxvID0gaXMubmEoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkcm9vbXMpICYgIWlzLm5hKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJGJhdGhyb29tcykKI21vbm9hbWJpZW50ZQpyb29tc19udWxvX2JhbmlvX3VuaWNvID0gcm9vbXNfbnVsb19iYW5pb19ub19udWxvICYgKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJGJhdGhyb29tcyA9PSAxKQpkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRyb29tc1tyb29tc19udWxvX2JhbmlvX3VuaWNvXSA8LSAxCnJvb21zX251bG9fYmFuaW9fdW5pY29fYmVkcm9vbXNfbnVsbyA9IHJvb21zX251bG9fYmFuaW9fdW5pY28gJiBpcy5uYShkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRiZWRyb29tcykKZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkYmVkcm9vbXNbcm9vbXNfbnVsb19iYW5pb191bmljb19iZWRyb29tc19udWxvXSA8LSAxCgpyb29tc19udWxvX21hc19kZV91bl9iYW5pbyA9IHJvb21zX251bG9fYmFuaW9fbm9fbnVsbyAmIChkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRiYXRocm9vbXMgPiAxKQpkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRyb29tc1tyb29tc19udWxvX21hc19kZV91bl9iYW5pb10gPC0gZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkYmF0aHJvb21zW3Jvb21zX251bG9fbWFzX2RlX3VuX2JhbmlvXSArIDEKCiNudWxsIGVuIGNhbnRpZGFkIGRlIGhhYml0YWNpb25lcwpiZWRyb29tc19udWxvX2JhbmlvX3VuaWNvID0gIWlzLm5hKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHJvb21zKSAmIGlzLm5hKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJGJlZHJvb21zKSAmIChkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRiYXRocm9vbXMgPT0gMSkgJiAoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkcm9vbXMgPT0gMSkKYmVkcm9vbXNfbnVsb19tYXNfZGVfdW5fYmFuaW8gPSAhaXMubmEoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkcm9vbXMpICYgaXMubmEoZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUkYmVkcm9vbXMpICYgKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJGJhdGhyb29tcyA+IDEpICYgKGRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJHJvb21zID4gMSkKI21vbm9hbWJpZW50ZQpkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRiZWRyb29tc1tiZWRyb29tc19udWxvX2JhbmlvX3VuaWNvXSA8LSAxCmRhdG9zX2ZpbHRyb19zdXBlcmZpY2llJGJlZHJvb21zW2JlZHJvb21zX251bG9fbWFzX2RlX3VuX2JhbmlvXSA8LSBkYXRvc19maWx0cm9fc3VwZXJmaWNpZSRyb29tc1tiZWRyb29tc19udWxvX21hc19kZV91bl9iYW5pb10gLSAxCgoKZGF0b3MgPC0gZGF0b3NfZmlsdHJvX3N1cGVyZmljaWUKCiNTYWNhbW9zIGxhIHByaW1lcmEgZW50cmFkYSBkZSBsYSBiYXNlIHBvciBzZXIgdW4gIkVkaWZpY2lvIGRlIGRlcGFydGFtZW50b3MgdGlwbyBQSCIgcXVlIGVzIG3DoXMgYmllbiB1biBjb25qdW50byBkZSA3IFBIcwpkYXRvc1sxLF0KZGF0b3MgPC0gZGF0b3NbLTEsIF0KCiNzZSBlbGltaW5hIHVuIGRhdG8gZHVwbGljYWRvCmRhdG9zW2MoMjQzLDMwNCksXQpkYXRvcyA8LWRhdG9zWy0zMDQsIF0KCmhpc3QoZGF0b3MkcHJpY2UsIG1haW4gPSAiIiwgeGxhYiA9ICJQcmVjaW8iLCB5bGFiID0gIkZyZWN1ZW5jaWEiKQpib3hwbG90KGRhdG9zJHByaWNlLCBtYWluID0gIlByZWNpbyIpCmhpc3QoZGF0b3Mkcm9vbXMsIG1haW4gPSAiIiwgeGxhYiA9ICJBbWJpZW50ZXMiLCB5bGFiID0gIkZyZWN1ZW5jaWEiKQpoaXN0KGRhdG9zJGJlZHJvb21zLCBtYWluID0gIiIsIHhsYWIgPSAiSGFiaXRhY2lvbmVzIiwgeWxhYiA9ICJGcmVjdWVuY2lhIikKaGlzdChkYXRvcyRiYXRocm9vbXMsIG1haW4gPSAiIiwgeGxhYiA9ICJCYcOxb3MiLCB5bGFiID0gIkZyZWN1ZW5jaWEiKQoKYGBgCgojIEFuw6FsaXNpcyBleHBsb3JhdG9yaW8geSBkZXNjcmlwdGl2bwoKU2UgcmVhbGl6YSB1biBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIHkgZGVzY3JpcHRpdm8gKEVEQSkgZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyBjdWFudGl0YXRpdmFzLiBTZSBwcmVzZW50YSBsYSBpbmZvcm1hY2nDs24gZW4gbGEgc2lndWllbnRlIHRhYmxhLCBjb250ZW5pZW5kbyBsYXMgc2lndWllbnRlcyBtZWRpZGFzIGRlc2NyaXB0aXZhczoKCkNhbnRpZGFkIGRlIGRhdG9zLCBtw61uaW1vLCBtw6F4aW1vLCBtZWRpYSwgbWVkaWFuYSwgbW9kYSwgdmFyaWFuemEsIGRlc3ZpYWNpw7NuIGVzdMOhbmRhciwgY29lZmljaWVudGUgZGUgdmFyaWFjacOzbiwgY3VhcnRpbCAxLCBjdWFydGlsIDMsIHJhbmdvIGludGVyY3VhcnTDrWxpY28sIE1BRCwgYXNpbWV0csOtYSwgY3VydG9zaXMuCgpgYGB7ciBmdW5jaW9uX3Jlc3VtZW4sIGluY2x1ZGU9RkFMU0V9CnJlc3VtZW4gPC0gZnVuY3Rpb24oZGF0b3MpewoKY29udGVvIDwtIGZ1bmN0aW9uKHgpIHtyZXR1cm4oc3VtKCFpcy5uYSh4KSkpfQpjb2VmdmFyIDwtIGZ1bmN0aW9uKHgpIHtyZXR1cm4oc2QoeCxuYS5ybT1UUlVFKS9tZWFuKHgsbmEucm09VFJVRSkpfQptb2RlIDwtIGZ1bmN0aW9uKHgpIHtyZXR1cm4oYXMubnVtZXJpYyhuYW1lcyh3aGljaC5tYXgodGFibGUoeCkpKSkpfQpjYW50aWRhZCA8LSBzYXBwbHkoZGF0b3MsIGNvbnRlbykKbWluaW1vIDwtIHNhcHBseShkYXRvcywgbWluLCBuYS5ybT1UUlVFKQptYXhpbW8gPC0gc2FwcGx5KGRhdG9zLCBtYXgsIG5hLnJtPVRSVUUpCm1lZGlhIDwtIHNhcHBseShkYXRvcywgbWVhbiwgbmEucm09VFJVRSkKbWVkaWFuYSA8LSBzYXBwbHkoZGF0b3MsIG1lZGlhbiwgbmEucm09VFJVRSkKbW9kYSA8LSBzYXBwbHkoZGF0b3MsIG1vZGUpCnZhcmlhbnphIDwtIHNhcHBseShkYXRvcywgdmFyLCBuYS5ybT1UUlVFKQpkZXN2aW9fZXN0YW5kYXIgPC0gc2FwcGx5KGRhdG9zLCBzZCwgbmEucm09VFJVRSkKY29lZl92YXJpYWNpb24gPC0gc2FwcGx5KGRhdG9zLCBjb2VmdmFyKQpjdWFydGlsMSA8LSBzYXBwbHkoZGF0b3MsIHF1YW50aWxlLCAwLjI1LCBuYS5ybT1UUlVFKQpjdWFydGlsMyA8LSBzYXBwbHkoZGF0b3MsIHF1YW50aWxlLCAwLjc1LCBuYS5ybT1UUlVFKQpSSUMgPC0gc2FwcGx5KGRhdG9zLCBJUVIsIG5hLnJtPVRSVUUpCk1BRCA8LSBzYXBwbHkoZGF0b3MsIG1hZCwgbmEucm09VFJVRSkKYXNpbWV0cmlhIDwtIHNhcHBseShkYXRvcywgc2tld25lc3MsIG5hLnJtPVRSVUUpCmt1cnRvc2lzIDwtIHNhcHBseShkYXRvcywga3VydG9zaXMsIG5hLnJtPVRSVUUpCnJlcyA8LSBhcy5kYXRhLmZyYW1lKGxpc3QoY2FudGlkYWQsbWluaW1vLG1heGltbyxtZWRpYSxtZWRpYW5hLG1vZGEsdmFyaWFuemEsZGVzdmlvX2VzdGFuZGFyLGNvZWZfdmFyaWFjaW9uLGN1YXJ0aWwxLGN1YXJ0aWwzLFJJQyxNQUQsYXNpbWV0cmlhLGt1cnRvc2lzKSkKY29sdW1uYXMgPC0gYygiQ2FudGlkYWQiLCAiTcOtbmltbyIsICJNw6F4aW1vIiwgIk1lZGlhIiwgIk1lZGlhbmEiLCAiTW9kYSIsICJWYXJpYW56YSIsICJEZXN2w61vIGVzdMOhbmRhciIsICJDb2VmLiBkZSB2YXJpYWNpw7NuIiwgIkN1YXJ0aWwgMSIsICJDdWFydGlsIDMiLCAiUmFuZ28gaW50ZXJjdWFydMOtbGljbyIsICJNQUQiLCAiQXNpbWV0csOtYSIsICJLdXJ0b3NpcyIpCmNvbG5hbWVzKHJlcykgPC0gY29sdW1uYXMKcmV0dXJuKHJlcyl9CmBgYAoKYGBge3IgZWRhX2N1YW50aXRhdGl2YXN9CnZhcnNfY3VhbnRpIDwtIGMoImxhdCIsICJsb24iLCAicm9vbXMiLCAiYmVkcm9vbXMiLCAiYmF0aHJvb21zIiwgInN1cmZhY2VfdG90YWwiLCAic3VyZmFjZV9jb3ZlcmVkIiwgInByaWNlIikKCmRhdG9zX2N1YW50aSA8LSBkYXRvc1ssIHZhcnNfY3VhbnRpXQoKcmVzdW1lbl9jdWFudGkgPC0gcmVzdW1lbihkYXRvc19jdWFudGkpCgpyZXN1bWVuX2N1YW50aQpgYGAKCiMgVGFibGEgZGUgZnJlY3VlbmNpYXMgeSBwb3JjZW50YWplcyBkZSBsYXMgdmFyaWFibGVzIGN1YWxpdGF0aXZhcwoKQSBjb250aW51YWNpw7NuIHNlIHByZXNlbnRhIHVuYSB0YWJsYSBkZSBmcmVjdWVuY2lhcyB5IHBvcmNlbnRhamUgcGFyYSBsYXMgdmFyaWFibGVzIGN1YWxpdGF0aXZhcyAoY2F0ZWfDs3JpY2FzKS4KRW4gbGEgcHJpbWVyYSB0YWJsYSBzZSBjb25zaWduYW4gbGEgY2FudGlkYWQgZGUgcmVnaXN0cm9zIGVuIGN1eWEgZGVzY3JpcGNpw7NuIGZpZ3Vyw7MgYWxndW5hIGRlIGxhcyBwYWxhYnJhcyBjbGF2ZSBlbnVtZXJhZGFzIHkgZWwgcG9yY2VudGFqZSBxdWUgcmVwcmVzZW50YW4gcmVzcGVjdG8gZGVsIHRvdGFsLiBMYXMgcGFsYWJyYXMgY2xhdmUgY29uIG1heW9yIGZyZWN1ZW5jaWEgZnVlcm9uOiBleHBlbnNhcywgdGVycmF6YSB5IGVzY2FsZXJhLgoKRW4gbGEgc2VndW5kYSB0YWJsYSBzZSBpbmRpY2EgcXXDqSBjYW50aWRhZCB5IHBvcmNlbnRhamUgZGUgcHJvcGllZGFkZXMgc2UgaGFsbGFyb24gcGFyYSBjYWRhIGJhcnJpbyBkZSBDYXBpdGFsIEZlZGVyYWwuIFNlIHB1ZWRlIGFwcmVjaWFyIHF1ZSBsb3MgYmFycmlvcyBtZWpvciByZXByZXNlbnRhZG9zIHNvbiBQYWxlcm1vLCBWaWxsYSBDcmVzcG8geSBBbG1hZ3JvLgoKYGBge3IgdGFibGFzX2N1YWxpdGF0aXZhc19jb2RpZ28sIGluY2x1ZGU9RkFMU0V9CmNvbHVtbmFzX2R1bW1pZXMgPC0gYygibHVtaW5vc28iLCAicmVjaWNsYWRvIiwgImV4cGVuc2FzIiwgImVzcGVjdGFjdWxhciIsICJxdWluY2hvIiwgInRlcnJhemEiLCAiZXNjYWxlcmEiLCAiZ2FsZXJpYSIpCgp0YWJsYV9mcmVjX2R1bW1pZXMgPC0gZnVuY3Rpb24oZGYsIGNvbHVtbmFzKXsKICB0YWJsYSA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gY2hhcmFjdGVyKDApLCBGcmVjdWVuY2lhID0gaW50ZWdlcigwKSwgUG9yY2VudGFqZSA9IG51bWVyaWMoMCksIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKICBmb3IgKGNvbHVtbmEgaW4gY29sdW1uYXMpIHsKICAgIGNvbnRlbyA8LSBzdW0oZGZbW2NvbHVtbmFdXSA9PSAxKQogICAgcHJvcG9yY2lvbiA8LSBjb250ZW8vc3VtKCFpcy5uYShkZltbY29sdW1uYV1dKSkKICAgIGZpbGEgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IGNvbHVtbmEsIEZyZWN1ZW5jaWEgPSBjb250ZW8sIFBvcmNlbnRhamUgPSBwcm9wb3JjaW9uKjEwMCkKICAgIHRhYmxhIDwtIGJpbmRfcm93cyh0YWJsYSwgZmlsYSkKICB9CiAgcmV0dXJuKHRhYmxhKQp9CmBgYAoKYGBge3IgdGFibGFzX2N1YWxpdGF0aXZhc30KCnRhYmxhX2ZyZWMgPC0gdGFibGFfZnJlY19kdW1taWVzKGRhdG9zLCBjb2x1bW5hc19kdW1taWVzKQp0YWJsYV9mcmVjCgpmcmVjX2JhcnJpb3MgPC0gdGFibGUoZGF0b3MkbDMpCnRvdGFsX2JhcnJpb3MgPC0gc3VtKCFpcy5uYShkYXRvcyRsMykpCnBvcmNlbnRhamVfYmFycmlvcyA8LSAxMDAqZnJlY19iYXJyaW9zL3RvdGFsX2JhcnJpb3MKZnJlY19kZiA8LSBhcy5kYXRhLmZyYW1lKGZyZWNfYmFycmlvcykKcG9yY2VudGFqZV9kZiA8LSBhcy5kYXRhLmZyYW1lKHBvcmNlbnRhamVfYmFycmlvcykKdGFibGFfYmFycmlvcyA8LSBjYmluZChmcmVjX2RmLCBwb3JjZW50YWplX2RmW1syXV0pCmNvbG5hbWVzKHRhYmxhX2JhcnJpb3MpIDwtIGMoIkJhcnJpbyIsIkZyZWN1ZW5jaWEiLCAiUG9yY2VudGFqZSIpCgp0YWJsYV9iYXJyaW9zW29yZGVyKC10YWJsYV9iYXJyaW9zWywyXSksXQoKYGBgCiMgR3JhZmljYWNpw7NuIGRlIGxhcyB2YXJpYWJsZXMgY3VhbGl0YXRpdmFzCkEgY29udGludWFjacOzbiBzZSBwcmVzZW50YW4gZG9zIGdyw6FmaWNvcyBxdWUgcmVwcmVzZW50YW4gbGEgY2FudGlkYWQgZGUgcmVnaXN0cm9zIGVuIGN1eWEgZGVzY3JpcGNpw7NuIGZpZ3Vyw7MgYWxndW5hIGRlIGxhcyBwYWxhYnJhcyBjbGF2ZSBwcmVzZW50YWRhcyBlbiBsYSB0YWJsYSBkZWwgcHVudG8gYW50ZXJpb3IuIFNlIG11ZXN0cmEgZnJlY3VlbmNpYSBhYnNvbHV0YSB5IHBvcmNlbnRhamUgZGVsIHRvdGFsIHJlc3BlY3RpdmFtZW50ZS4KCkVuIGNvbmNvcmRhbmNpYSBjb24gbGEgc2VndW5kYSB0YWJsYSBwcmVzZW50YWRhIGVuIGVsIHB1bnRvIGFudGVyaW9yIHNlIHJlYWxpesOzIHVuIGdyw6FmaWNvIGRlIHRvcnRhcywgY29uIGxvcyBwb3JjZW50YWplcyBkZSBwcm9waWVkYWRlcyBoYWxsYWRvcyBlbiBsb3MgcHJpbmNpcGFsZXMgYmFycmlvcyBkZSBDYXBpdGFsIEZlZGVyYWwuCgoKYGBge3IgZ3JhZmljb3NfY3VhbGl0YXRpdmFzfQpnZ3Bsb3QodGFibGFfZnJlYyxhZXMoeD1mYWN0b3IoVmFyaWFibGUpLHk9RnJlY3VlbmNpYSkpKwogIGdlb21fY29sKGNvbG9yPSdibGFjaycsZmlsbD0nY3lhbjMnKSsKICB4bGFiKCcnKQoKZ2dwbG90KHRhYmxhX2ZyZWMsYWVzKHg9ZmFjdG9yKFZhcmlhYmxlKSx5PVBvcmNlbnRhamUpKSsKICBnZW9tX2NvbChjb2xvcj0nYmxhY2snLGZpbGw9J2N5YW4zJykrCiAgeGxhYignJykKCnBvcmNlbnRhamVzIDwtIHByb3AudGFibGUoZnJlY19iYXJyaW9zKSAqIDEwMApwb3JjZW50YWplc19vcmRlbmFkb3MgPC0gc29ydChwb3JjZW50YWplcywgZGVjcmVhc2luZyA9IFRSVUUpCm5yb19jYXRlZ29yaWFzID0gMTIKY2F0ZWdvcmlhc190b3AgPC0gbmFtZXMocG9yY2VudGFqZXNfb3JkZW5hZG9zKVsxOm5yb19jYXRlZ29yaWFzXQpvdHJvc19wb3JjZW50YWplIDwtIHN1bShwb3JjZW50YWplc19vcmRlbmFkb3NbNzpsZW5ndGgocG9yY2VudGFqZXNfb3JkZW5hZG9zKV0pCm51ZXZhX3RhYmxhIDwtIGMocG9yY2VudGFqZXNfb3JkZW5hZG9zWzE6bnJvX2NhdGVnb3JpYXNdLCAiT3Ryb3MgYmFycmlvcyIgPSBvdHJvc19wb3JjZW50YWplKQpwaWUobnVldmFfdGFibGEsIGxhYmVscyA9IHBhc3RlKG5hbWVzKG51ZXZhX3RhYmxhKSwgIjogIiwgcm91bmQobnVldmFfdGFibGEsIDIpLCAiJSIpLCBtYWluID0gIlBIIHBvciBiYXJyaW8iKQpgYGAKCiMgQ2xhc2lmaWNhY2nDs24gamVyw6FycXVpY2EKQSBjb250aW51YWNpw7NuIHNlIHJlYWxpesOzIHVuIGFuw6FsaXNpcyBkZSBjbHVzdGVyaW5nIHRlbmllbmRvIGVuIGN1ZW50YSBsYXMgc2lndWllbnRlcyB2YXJpYWJsZXMgY3VhbnRpdGF0aXZhczoKCkxhdGl0dWQsIExvbmdpdHVkLCBOw7ptZXJvIGRlIGFtYmllbnRlcywgU3VwZXJmaWNpZSB0b3RhbCwgU3VwZXJmaWNpZSBjdWJpZXJ0YSB5IFByZWNpby4KCk5vIHNlIGluY2x1eWVyb24gbGFzIHZhcmlhYmxlcyBDYW50aWRhZCBkZSBoYWJpdGFjaW9uZXMgeSBDYW50aWRhZCBkZSBiYcOxb3MgcG9yIHNlciB2YXJpYWJsZXMgY29ycmVsYWNpb25hZGFzIGVudHJlIHPDrSB5IGNvbiBuw7ptZXJvIGRlIGFtYmllbnRlcy4gTGFzIHZhcmlhYmxlcyBzZSBlc3RhbmRhcml6YXJvbiBwcmV2aWFtZW50ZSBhbCBhbsOhbGlzaXMuCgpTZSB0b23DsyBsYSBkaXN0YW5jaWEgZXVjbMOtZGVhIHBhcmEgZWxhYm9yYXIgbGEgbWF0cml6IGRlIGRpc3RhbmNpYXMgbyBkaXNpbWlsaXR1ZC4gQ29tbyBhbGdvcml0bW8gZGUgbGlnYW1pZW50byBzZSBlbXBsZcOzIGVsIHByb21lZGlvIChhdmVyYWdlIGxpbmthZ2UpIHBvciBwcmVzZW50YXIgZWwgbWF5b3Igw61uZGljZSBkZSBjb3JyZWxhY2nDs24gY29mZW7DqXRpY2EsIHJlc3BlY3RvIGRlIGxhcyBvdHJhcyBhbHRlcm5hdGl2YXM6IGVsIGNvbXBsZXRvLCBlbCBzaW1wbGUsIHkgV2FyZC4KClNlIHV0aWxpesOzIGVsIG3DqXRvZG8gZGVsIGNvZG8gKGVsYm93KSBwYXJhIGZpamFyIGVsIG7Dum1lcm8gZGUgY2x1c3RlcnMuIEEgcGFydGlyIGRlbCBncsOhZmljbyBkZSBzdW1hcyBkZSBjdWFkcmFkb3MgZGVudHJvIGRlIGxvcyBjbHVzdGVycyBlbiBmdW5jacOzbiBkZWwgbsO6bWVybyBkZSBjbHVzdGVycywgc2UgdG9tw7MgZWwgcHVudG8gZG9uZGUgc2Ugb2JzZXJ2YWJhIHVuYSBkaXNtaW51Y2nDs24gc2lnbmlmaWNhdGl2YSBkZSBkaWNoYSBtYWduaXR1ZCwgcmVzdWx0YW5kbyBlbiBzZWlzIGdydXBvcy4KCkEgY29udGludWFjacOzbiwgc2UgZ3JhZmljw7MgZWwgZGVuZHJvZ3JhbWEgY29ycmVzcG9uZGllbnRlLCB5IHNlZ8O6biBjb24gZWwgY3JpdGVyaW8gZWxlZ2lkbywgc2UgcmVhbGl6w7MgZWwgY29ydGUgZW4gc2VpcyBncnVwb3MuIEVuIGVzdGUgZGVuZHJvZ3JhbWEgeWEgc2UgcHVlZGUgYXByZWNpYXIgcXVlIGV4aXN0ZSB1biBncnVwbyBtYXlvcml0YXJpbyAoMzc5IG9ic2VydmFjaW9uZXMpLCBsdWVnbyB1biBncnVwbyBtaW5vcml0YXJpbyAoMjMgb2JzZXJ2YWNpb25lcyksIHkgbHVlZ28gZ3J1cG9zIGNvbmZvcm1hZG9zIHBvciBwb2NhcyBvYnNlcnZhY2lvbmVzLCBvIHPDs2xvIHVuYSBvYnNlcnZhY2nDs24uCgpFc3RvIHNlIG9ic2VydmEgbWVqb3IgY3VhbmRvIHNlIGdyYWZpY2FuIGxvcyBncnVwb3MgZW4gbG9zIGVqZXMgZGVsIFBDQSwgZG9uZGUgc2UgZGV0ZWN0YSBxdWUgZWwgZ3J1cG8gbWF5b3JpdGFyaW8gZXN0w6EgaW50ZWdyYWRvIHBvciBwcm9waWVkYWRlcyBkZSBtZW5vciBwcmVjaW8sIHN1cGVyZmljaWUgeSBjYW50aWRhZCBkZSBhbWJpZW50ZXMuIEVsIGdydXBvIHF1ZSBsZSBzaWd1ZSBlbiBuw7ptZXJvIHNlIGFzb2NpYSBhIHZhbG9yZXMgbcOhcyBhbHRvcyBkZSBkaWNoYXMgdmFyaWFibGVzLgoKTm8gZXMgdGFuIHNpZ25pZmljYXRpdm8gZWwgYW7DoWxpc2lzIG1pbnVjaW9zbyBkZSBsb3MgZ3J1cG9zIGRlIHVuYXMgcG9jYXMgb2JzZXJ2YWNpb25lcyBxdWUgc2UgZm9ybWFuLCB5YSBxdWUgZXN0b3Mgc2UgZGlzdGluZ3VlbiBwb3IgbGEgY29uanVuY2nDs24gZGUgY2FyYWN0ZXLDrXN0aWNhcyBwYXJ0aWN1bGFyZXMgcXVlIGxvcyBzZXBhcmFuIGRlIGxvcyBjbHVzdGVycyBtw6FzIGdyYW5kZXM6IHByZWNpbyBtdXkgZWxldmFkbywgc3VwZXJmaWNpZSBjdWJpZXJ0YSBtdXkgYWx0YSAocHVlZGUgc2VyIHVuIGVmZWN0byBkZSBsYSBpbXB1dGFjacOzbiBxdWUgcmVhbGl6YW1vcyBlbiBudWVzdHJvIHByZXByb2Nlc2FtaWVudG8gZGUgbG9zIGRhdG9zKS4KCkVuIGVzdGUgc2VudGlkbywgc2UgYW5hbGl6YXJvbiBsb3MgcmVzdWx0YWRvcyBkZSByZWFsaXphciB1biBjbHVzdGVyaW5nIGplcsOhcnF1aWNvLCBjb24gZWwgbcOpdG9kbyBkZSBsaWdhbWllbnRvIHByb21lZGlvLCB5IGZpamFuZG8gdW4gayA9IDIgKG5vIHNlIG11ZXN0cmFuIGxvcyByZXN1bHRhZG9zKS4gU2UgdmlvIHF1ZSBsb3MgZ3J1cG9zIGZvcm1hZG9zIG5vIGVyYW4gbG9zIGVzcGVyYWJsZXMsIHF1ZWRhbmRvIHVuYXMgcG9jYXMgb2JzZXJ2YWNpb25lcyBleHRyZW1hcyBzZXBhcmFkYXMgZGVsIHJlc3RvLgoKQ2FiZSBzZcOxYWxhciBxdWUgZXN0YW5kbyBsYXMgdmFyaWFibGVzIGVzdGFuZGFyaXphZGFzLCBlIGluY2x1ecOpbmRvc2UgZG9zIHZhcmlhYmxlcyBkZSBzdXBlcmZpY2llICh0b3RhbCB5IGN1YmllcnRhKSwgZXMgY29tbyBzaSwgY29uY2VwdHVhbG1lbnRlLCBsYSBzdXBlcmZpY2llIHBlc2FyYSBlbCBkb2JsZSBxdWUgZWwgcmVzdG8gZGUgbGFzIHZhcmlhYmxlcykuIEVzdGUgcHVudG8geSBlbCB0ZW5lciB2YXJpYWJsZXMgY29ycmVsYWNpb25hZGFzIGNvbW8gZWwgbsO6bWVybyBkZSBhbWJpZW50ZXMsIGxhIHN1cGVyZmljaWUgeSBlbCBwcmVjaW8sIHB1ZWRlbiBhZmVjdGFyIGxvcyByZXN1bHRhZG9zIGRlbCBjbHVzdGVyaW5nLgoKCmBgYHtyIGNsdXN0ZXJpbmd9CnZhcnNfY3VhbnRpX2NsdXN0ZXIgPC0gYygibGF0IiwgImxvbiIsICJyb29tcyIsICJzdXJmYWNlX3RvdGFsIiwgInN1cmZhY2VfY292ZXJlZCIsICJwcmljZSIpCgpkYXRvc19jdWFudGlfY2x1c3RlciA8LSBuYS5vbWl0KGRhdG9zWywgdmFyc19jdWFudGlfY2x1c3Rlcl0pCgpkYXRvc19jdWFudGlfY2x1c3Rlcl9zdGQgPC0gc2NhbGUoZGF0b3NfY3VhbnRpX2NsdXN0ZXIpCm1hdF9kaXN0IDwtIGRpc3QoeCA9IGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpIAoKaGNfY29tcGxldGUgPC0gaGNsdXN0KGQgPSBtYXRfZGlzdCwgbWV0aG9kID0gImNvbXBsZXRlIikgCmhjX2F2ZXJhZ2UgPC0gaGNsdXN0KGQgPSBtYXRfZGlzdCwgbWV0aG9kID0gImF2ZXJhZ2UiKQpoY19zaW5nbGUgPC0gaGNsdXN0KGQgPSBtYXRfZGlzdCwgbWV0aG9kID0gInNpbmdsZSIpCmhjX3dhcmQgPC0gaGNsdXN0KGQgPSBtYXRfZGlzdCwgbWV0aG9kID0gIndhcmQuRDIiKQpjb3IoeCA9IG1hdF9kaXN0LCBjb3BoZW5ldGljKGhjX2NvbXBsZXRlKSkKY29yKHggPSBtYXRfZGlzdCwgY29waGVuZXRpYyhoY19hdmVyYWdlKSkKY29yKHggPSBtYXRfZGlzdCwgY29waGVuZXRpYyhoY19zaW5nbGUpKQpjb3IoeCA9IG1hdF9kaXN0LCBjb3BoZW5ldGljKGhjX3dhcmQpKQoKI2F2ZXJhZ2UgbGlua2FnZSBlcyBsYSBxdWUgcmVzdWx0w7MgbWVqb3IsIHNlZ3VpbW9zIGNvbiBlc2EKbGlicmFyeShmYWN0b2V4dHJhKQpmdml6X25iY2x1c3QoeCA9IGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgRlVOY2x1c3RlciA9IGhjdXQsaGNfbWV0aG9kID0iYXZlcmFnZSIsc3RhbmQ9VFJVRSwgbWV0aG9kID0gIndzcyIsIGRpc3MgPSBkaXN0KGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpKQoKI2VsIGNvZWZpY2llbnRlIGRlIFNpbGhvdWV0dGUgZGEgMiBncnVwb3MKI2Z2aXpfbmJjbHVzdCh4ID0gZGF0b3NfY3VhbnRpX2NsdXN0ZXJfc3RkLCBGVU5jbHVzdGVyID0gaGN1dCxoY19tZXRob2QgPSJhdmVyYWdlIixzdGFuZD1UUlVFLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIsIGRpc3MgPSBkaXN0KGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpKQojZWwgZXN0YWTDrXN0aWNvIGdhcCBkYSAxIGdydXBvCiNmdml6X25iY2x1c3QoeCA9IGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgRlVOY2x1c3RlciA9IGhjdXQsaGNfbWV0aG9kID0iYXZlcmFnZSIsc3RhbmQ9VFJVRSwgbWV0aG9kID0gImdhcCIsIGRpc3MgPSBkaXN0KGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpKQpncnVwb3MgPSA2CnBsb3QoaGNfYXZlcmFnZSwgbGFiZWxzID0gRkFMU0UpCmNsdXN0ZXJzID0gY3V0cmVlKGhjX2F2ZXJhZ2UsIGsgPSBncnVwb3MpCnJlY3QuaGNsdXN0KGhjX2F2ZXJhZ2UsIGs9Z3J1cG9zLCBib3JkZXI9InJlZCIpCgpmdml6X2NsdXN0ZXIob2JqZWN0ID0gbGlzdChkYXRhID0gZGF0b3NfY3VhbnRpX2NsdXN0ZXJfc3RkLCBjbHVzdGVyID0gY3V0cmVlKGhjX2F2ZXJhZ2UsIGsgPSBncnVwb3MpKSwgZWxsaXBzZS50eXBlID0gImNvbnZleCIsIHJlcGVsID0gVFJVRSwgc2hvdy5jbHVzdC5jZW50ID0gRkFMU0UpICsgdGhlbWVfYncoKQoKY2x1c3Rlcl9wY2EgPC0gcHJjb21wKGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCkKZnZpel9wY2FfYmlwbG90KGNsdXN0ZXJfcGNhLCBsYWJlbCA9ICJ2YXIiKQoKI0hheSBQSHMgaW5kaXZpZHVhbGVzIHF1ZSBzZSBzZXBhcmFuIHBvciBzdXMgY2FyYWN0ZXLDrXN0aWNhcywgcGVybyBiw6FzaWNhbWVudGUgc2UgZGlzdGluZ3VlbiBkb3MgZ3JhbmRlcyBncnVwb3MKI0dydXBvcyBtaW5vcml0YXJpb3M6IGRldGFsbGUgZGUgbG9zIHJlZ2lzdHJvcwpkYXRvc19jdWFudGlfY2x1c3RlcltjKDM5MywyNjEsMzY0KSwgXQpkYXRvc19jdWFudGlfY2x1c3Rlclt3aGljaChjbHVzdGVycyA9PSAzKSwgXQpkYXRvc1tjKDM5MywyNjEsMzY0KSwgXQpkYXRvc1t3aGljaChjbHVzdGVycyA9PSAzKSwgXQoKI0NhbnRpZGFkZXMgZGUgbG9zIGdydXBvcyBtYXlvcml0YXJpb3M6CnN1bShjbHVzdGVycyA9PSAxKQpzdW0oY2x1c3RlcnMgPT0gMikKI01lZGlkYXMgZGUgcmVzdW1lbiBkZSBsb3MgY2x1c3RlcnMgbWF5b3JpdGFyaW9zCnJlc3VtZW4oZGF0b3NfY3VhbnRpW3doaWNoKGNsdXN0ZXJzID09IDEpLF0pCnJlc3VtZW4oZGF0b3NfY3VhbnRpW3doaWNoKGNsdXN0ZXJzID09IDIpLF0pCmBgYApQYXJhIGNvbXBsZW1lbnRhciBlbCBhbsOhbGlzaXMgYW50ZXJpb3IsIHV0aWxpemFtb3MgYWRlbcOhcyBlbCBtw6l0b2RvIGRlIGxpZ2FtaWVudG8gV2FyZCBvIGRlIHZhcmlhbnphIG3DrW5pbWEgcGFyYSBlbCBjbHVzdGVyaW5nIGplcsOhcnF1aWNvLiBFbCBncsOhZmljbyBkZSBsYSBzdW1hIGRlIGN1YWRyYWRvcyBkZW50cm8gZW4gZnVuY2nDs24gZGVsIG7Dum1lcm8gZGUgZ3J1cG9zLCBubyBlcyBkZSBpbnRlcnByZXRhY2nDs24gdGFuIGlubWVkaWF0YSBjb21vIGVuIGVsIGNhc28gZGVsIGxpZ2FtaWVudG8gcHJvbWVkaW8gcHVlc3RvIHF1ZSBoYXkgdW4gZGVzY2Vuc28gYWJydXB0byBwYXJhIGs9MiwgcGVybyBzaWd1ZSBkaXNtaW51eWVuZG8gY29uc2lkZXJhYmxlbWVudGUgaGFzdGEgaz01LiBTZSBlbGlnZSBrPTUgKGVsIG3DqXRvZG8gZGUgU2lsaG91ZXR0ZSBhcnJvamEgdW4gcmVzdWx0YWRvIHNpbWlsYXIsIHBvciBlc28gbm8gbG8gc2UgbG8gaW5jbHV5ZSkuCgpFbCBhbsOhbGlzaXMgbcOhcyBpbm1lZGlhdG8gZGVsIGRlbmRyb2dyYW1hIHJlc3VsdGFudGUgcGVybWl0ZSBhcHJlY2lhciBxdWUgc2UgZm9ybWFyb24gZ3J1cG9zIG3DoXMgaG9tb2fDqW5lb3MsIGVuIGNvbnNvbmFuY2lhIGNvbiBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZWwgbcOpdG9kbyBkZSBsaWdhbWllbnRvIGVsZWdpZG8uIFNpIHNlIG9ic2VydmEgY8OzbW8gcXVlZGFyb24gYWdydXBhZG9zIGxvcyBwdW50b3MgZW4gbG9zIGVqZXMgZGVsIFBDQSwgc2UgdmVyw6EgcXVlIGVzdGUgYWdydXBhbWllbnRvIGVzIHNpbWlsYXIgYWwgYXJyb2phZG8gcG9yIGVsIG3DqXRvZG8gZGUgay1tZWFucyAoeSBsYSBpbnRlcnByZXRhY2nDs24gZGUgbG9zIGdydXBvcyB0YW1iacOpbiBzZXLDoSBzaW1pbGFyKS4gQXNpbWlzbW8sIHVuIHB1bnRvIGEgZGVzdGFjYXIgZXMgcXVlIGxvcyBncnVwb3MgdGFtYmnDqW4gdGllbmVuIHVuYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIG3DoXMgaG9tb2fDqW5lYSBlbnRyZSBlbGxvcyB5LCBhIGRpZmVyZW5jaWEgZGVsIG3DqXRvZG8gZGUgbGlnYW1pZW50byBwcm9tZWRpbywgbm8gbm9zIGFycm9qYSBncnVwb3MgY29uIHVuYSwgZG9zLCBvIHVuYXMgcG9jYXMgb2JzZXJ2YWNpb25lcy4KClRhbWJpw6luIHNlIG11ZXN0cmFuIGxvcyByZXN1bHRhZG9zIGRlIGxhIGNvcnJpZGEgY29uIGsgPSAyLiBFbiBsb3MgZWplcyBkZWwgUENBLCBzZSBwdWVkZSBhcHJlY2lhciBxdWUgc2UgZm9ybWFuIGRvcyBncnVwb3MgY29uIHVuIG7Dum1lcm8gY29uc2lkZXJhYmxlIGRlIG9ic2VydmFjaW9uZXMsIHF1ZSBzZSBjb25kaWNlbiBjb24gZGlzdGludG9zIHJhbmdvcyBkZSBwcmVjaW8sIHN1cGVyZmljaWUgdG90YWwgeSBjdWJpZXJ0YSwgeSBjYW50aWRhZCBkZSBhbWJpZW50ZXMuCgpgYGB7ciBjbHVzdGVyaW5nX3dhcmR9CiNXYXJkCgpmdml6X25iY2x1c3QoeCA9IGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgRlVOY2x1c3RlciA9IGhjdXQsaGNfbWV0aG9kID0id2FyZCIsc3RhbmQ9VFJVRSwgbWV0aG9kID0gIndzcyIsIGRpc3MgPSBkaXN0KGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpKQojZWwgY29lZmljaWVudGUgZGUgU2lsaG91ZXR0ZSBkYSAyIGdydXBvcwojZnZpel9uYmNsdXN0KHggPSBkYXRvc19jdWFudGlfY2x1c3Rlcl9zdGQsIEZVTmNsdXN0ZXIgPSBoY3V0LGhjX21ldGhvZCA9IndhcmQiLHN0YW5kPVRSVUUsIG1ldGhvZCA9ICJzaWxob3VldHRlIiwgZGlzcyA9IGRpc3QoZGF0b3NfY3VhbnRpX2NsdXN0ZXJfc3RkLCBtZXRob2QgPSAiZXVjbGlkZWFuIikpCgpncnVwb3Nfd2FyZCA9IDUKcGxvdChoY193YXJkLCBsYWJlbHMgPSBGQUxTRSkKY2x1c3RlcnNfd2FyZCA9IGN1dHJlZShoY193YXJkLCBrID0gZ3J1cG9zX3dhcmQpCnJlY3QuaGNsdXN0KGhjX3dhcmQsIGs9Z3J1cG9zX3dhcmQsIGJvcmRlcj0icmVkIikKCmZ2aXpfY2x1c3RlcihvYmplY3QgPSBsaXN0KGRhdGEgPSBkYXRvc19jdWFudGlfY2x1c3Rlcl9zdGQsIGNsdXN0ZXIgPSBjdXRyZWUoaGNfd2FyZCwgayA9IGdydXBvc193YXJkKSksIGVsbGlwc2UudHlwZSA9ICJjb252ZXgiLCByZXBlbCA9IFRSVUUsIHNob3cuY2x1c3QuY2VudCA9IEZBTFNFKSArIHRoZW1lX2J3KCkKCmZvciAoaSBpbiAxOmdydXBvc193YXJkKSB7CiAgcHJpbnQocGFzdGUoIk9ic2VydmFjaW9uZXMgZW4gZWwgZ3J1cG8iLCBpLCAiOiIsIHN1bShjbHVzdGVyc193YXJkID09IGkpKSkKfQoKZ3J1cG9zX3JlZHVjaWRvID0gMgpwbG90KGhjX3dhcmQsIGxhYmVscyA9IEZBTFNFKQpjbHVzdGVyc19yZWR1Y2lkbyA9IGN1dHJlZShoY193YXJkLCBrID0gZ3J1cG9zX3JlZHVjaWRvKQpyZWN0LmhjbHVzdChoY193YXJkLCBrPWdydXBvc19yZWR1Y2lkbywgYm9yZGVyPSJyZWQiKQoKZnZpel9jbHVzdGVyKG9iamVjdCA9IGxpc3QoZGF0YSA9IGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgY2x1c3RlciA9IGN1dHJlZShoY193YXJkLCBrID0gZ3J1cG9zX3JlZHVjaWRvKSksIGVsbGlwc2UudHlwZSA9ICJjb252ZXgiLCByZXBlbCA9IFRSVUUsIHNob3cuY2x1c3QuY2VudCA9IEZBTFNFKSArIHRoZW1lX2J3KCkKCmZ2aXpfcGNhX2JpcGxvdChjbHVzdGVyX3BjYSwgbGFiZWwgPSAidmFyIikKCgpmb3IgKGkgaW4gMTpncnVwb3NfcmVkdWNpZG8pIHsKICBwcmludChwYXN0ZSgiT2JzZXJ2YWNpb25lcyBlbiBlbCBncnVwbyIsIGksICI6Iiwgc3VtKGNsdXN0ZXJzX3JlZHVjaWRvID09IGkpKSkKfQoKYGBgCgojIEstbWVhbnMKClBhcmEgZWxlZ2lyIGxhIGNhbnRpZGFkIMOzcHRpbWEgZGUgZ3J1cG9zLCBlbCB2YWxvciBkZSBrLCBzZSBlbXBsZWEgbnVldmFtZW50ZSBlbCBtw6l0b2RvIGRlbCBjb2RvIHBvciBjb25zaWRlcmFyIHF1ZSDDqXN0ZSBlcyBlbCBxdWUgYnJpbmRhIG3DoXMgaW5mb3JtYWNpw7NuIGVuIG51ZXN0cm8gY2FzbyBwYXJ0aWN1bGFyIGRlIGFwbGljYWNpw7NuLiBMYSBzdW1hIGRlIGN1YWRyYWRvcyBkZW50cm8gcGFyZWNlIHJlZHVjaXJzZSBlbiBmb3JtYSBtw6FzIGRyw6FzdGljYSBoYXN0YSBrPTUsIHkgbHVlZ28gc2UgZXN0YWJpbGl6YS4gRXMgcG9yIGVzdG8gcXVlIHJlYWxpemFtb3MgZWwgYW7DoWxpc2lzIGRlIGxhcyBrLW1lZGlhcyBjb24gdW4gayBmaWphZG8gZW4gY2luY28gZ3J1cG9zLgoKRGUgZ3JhZmljYXIgZWwgcmVzdWx0YWRvIGRlbCBhbsOhbGlzaXMgZW4gbG9zIHByaW1lcm9zIGRvcyBlamVzIGRlbCBQQ0EsIHNlIHB1ZWRlIGNvbmNsdWlyIGxvIHNpZ3VpZW50ZS4gRW4gcHJpbWVyIGx1Z2FyLCBlbCBtw6l0b2RvIGhhIGZvcm1hZG8gdHJlcyBncnVwb3MgbcOhcyBhIGxhIGl6cXVpZXJkYSwgY29uIHByZWNpbyB5IHN1cGVyZmljaWUgbcOhcyBiYWpvcywgcXVlIHNlIGRpc3Rpbmd1ZW4gZW50cmUgZWxsb3MgcG9yIGxhIGxhdGl0dWQsIGVzIGRlY2lyIHBvciBsYSB1YmljYWNpw7NuIGdlb2dyw6FmaWNhLiBTZWd1aWRhbWVudGUsIHNlIHRpZW5lIGVsIGdydXBvIDEsIGNvbiB2YWxvcmVzIGRlIHByZWNpbyB5IHN1cGVyZmljaWUgaW50ZXJtZWRpb3MsIHkgcG9yIMO6bHRpbW8gYWwgZ3J1cG8gNSBxdWUgcmXDum5lIGxhcyBwcm9waWVkYWRlcyBjb24gdmFsb3JlcyBtw6FzIGV4dHJlbW9zIGRlIHByZWNpbyB5IHN1cGVyZmljaWUuIEVzdGUgw7psdGltbyBncnVwbyByZXN1bHRhIGVuIHVuIGFncnVwYW1pZW50byB1biB0YW50byBhcnRpZmljaWFsLCBwb3JxdWUgY29tbyBwb2RlbW9zIGFwcmVjaWFyIGVuIGVsIGJpcGxvdCwgbGEgZGlzdGFuY2lhIGFsIGNlbnRyb2lkZSBlcyBtdXkgdmFyaWFibGUgZW50cmUgbGFzIG9ic2VydmFjaW9uZXMgZGVsIGdydXBvLCB5IGVuIGFsZ3Vub3MgY2Fzb3MgY29tcGFyYXRpdmFtZW50ZSBncmFuZGUuCgpDYWJlIHNlbmFsYXIgcXVlIGVzdGUgbcOpdG9kbyBoYSBhcnJvamFkbyBncnVwb3MgY29uc2lkZXJhYmxlbWVudGUgbcOhcyBlcXVpbGlicmFkb3MgZW4gY3VhbnRvIGEgbGEgY2FudGlkYWQgZGUgb2JzZXJ2YWNpb25lcyBxdWUgY29udGllbmVuIHF1ZSBlbCBtw6l0b2RvIGRlIGNsdXN0ZXJpbmcgamVyw6FycXVpY28gY29uIG3DqXRvZG8gZGUgbGlnYW1pZW50byBwcm9tZWRpby4gQSBlc3RvIHNlIHB1ZWRlIGFncmVnYXIgZWwgaGVjaG8gZGUgcXVlIGxhIGRpZmVyZW5jaWEgbm8gcHVlZGUgcHJvdmVuaXIgZGUgbG9zIGRhdG9zLCB5YSBxdWUgc2UgZW1wbGVhcm9uIGxhcyBtaXNtYXMgdmFyaWFibGVzIChlc3RhbmRhcml6YWRhcykgcGFyYSBhbWJvcyBhbsOhbGlzaXMuIExhIGRpZmVyZW5jaWEgb2JzZXJ2YWRhIHByb3ZpZW5lIGRlbCBtw6l0b2RvIGRlIGstbWVhbnMsIHF1ZSBzZSBiYXNhIGVuIGNhbGN1bGFyIGRpc3RhbmNpYXMgYSBjZW50cm9pZGVzLCB5IHF1ZSB0aWVuZGUgYSBjb25mb3JtYXIgZ3J1cG9zIGVzZsOpcmljb3MuCgoKYGBge3Igay1tZWFuc30KZnZpel9uYmNsdXN0KHggPSBkYXRvc19jdWFudGlfY2x1c3Rlcl9zdGQsIEZVTmNsdXN0ZXIgPSBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiLCAKICAgICAgICAgICAgIGRpc3MgPSBkaXN0KGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpKSAjKyAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDUsIGxpbmV0eXBlID0gMikKI0VsIGNvZWZpY2llbnRlIGRlIFNpbGhvdWV0dGUgZGEgMiBncnVwb3MKI2Z2aXpfbmJjbHVzdCh4ID0gZGF0b3NfY3VhbnRpX2NsdXN0ZXJfc3RkLCBGVU5jbHVzdGVyID0ga21lYW5zLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIsIAojICAgICAgICAgICAgIGRpc3MgPSBkaXN0KGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpKQojRWwgZXN0YWTDrXN0aWNvIGdhcCBkYSAxIGdydXBvCiNmdml6X25iY2x1c3QoeCA9IGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgRlVOY2x1c3RlciA9IGttZWFucywgbWV0aG9kID0gImdhcF9zdGF0IiwgCiMgICAgICAgICAgICAgZGlzcyA9IGRpc3QoZGF0b3NfY3VhbnRpX2NsdXN0ZXJfc3RkLCBtZXRob2QgPSAiZXVjbGlkZWFuIikpCgpzZXQuc2VlZCg4OTMpCmttX2NsdXN0ZXJzIDwtIGttZWFucyh4ID0gZGF0b3NfY3VhbnRpX2NsdXN0ZXJfc3RkLCBjZW50ZXJzID0gNSwgbnN0YXJ0ID0gMjUpCmZ2aXpfY2x1c3RlcihvYmplY3QgPSBrbV9jbHVzdGVycywgZGF0YSA9IGRhdG9zX2N1YW50aV9jbHVzdGVyX3N0ZCwgc2hvdy5jbHVzdC5jZW50ID0gVFJVRSwgZWxsaXBzZS50eXBlID0gImV1Y2xpZCIsIHN0YXIucGxvdCA9IFRSVUUsIHJlcGVsID0gVFJVRSkgKyAKICB0aGVtZV9idygpICMgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmNsdXN0ZXJfcGNhIDwtIHByY29tcChkYXRvc19jdWFudGlfY2x1c3Rlcl9zdGQpCmZ2aXpfcGNhX2JpcGxvdChjbHVzdGVyX3BjYSwgbGFiZWwgPSAidmFyIikKCmttX2NsdXN0ZXJzJHNpemUKYGBgCgojIFBDQQpBIGNvbnRpbnVhY2nDs24gc2UgcmVhbGl6w7MgdW4gYW7DoWxpc2lzIGRlIGNvbXBvbmVudGVzIHByaW5jaXBhbGVzIHRlbmllbmRvIGVuIGN1ZW50YSBsYXMgc2lndWllbnRlcyB2YXJpYWJsZXMgY3VhbnRpdGF0aXZhczoKCkxhdGl0dWQsIExvbmdpdHVkLCBOw7ptZXJvIGRlIGFtYmllbnRlcywgQ2FudGlkYWQgZGUgYmHDsW9zLCBDYW50aWRhZCBkZSBoYWJpdGFjaW9uZXMsIFN1cGVyZmljaWUgdG90YWwsIFN1cGVyZmljaWUgY3ViaWVydGEgeSBQcmVjaW8uCgpMYXMgdmFyaWFibGVzIHNlIGVzdGFuZGFyaXphcm9uIHByZXZpYW1lbnRlIGFsIGFuw6FsaXNpcy4KRW4gcHJpbWVyIGx1Z2FyLCBkZWwgZ3LDoWZpY28gZGUgY29ycmVsYWNpb25lcyBzZSBwdWVkZSBleHRyYWVyIHF1ZSBzYWx2byBwb3IgbGEgdWJpY2FjacOzbiBnZW9ncsOhZmljYSwgZWwgcmVzdG8gZGUgbGFzIHZhcmlhYmxlcyBzZSBlbmN1ZW50cmFuIGNvcnJlbGFjaW9uYWRhcyBwb3NpdGl2YW1lbnRlLCBlcyBkZWNpciwgZW4gZm9ybWEgZGlyZWN0YSwgZW50cmUgc8OtLgoKU2kgc2UgYW5hbGl6YSBlbCBwb3JjZW50YWplIGRlIGxhIHZhcmlhbnphIGV4cGxpY2FkYSBhIG1lZGlkYSBxdWUgc2UgYWdyZWdhbiBjb21wb25lbnRlcyBwcmluY2lwYWxlcywgc2UgcHVlZGUgZGV0ZWN0YXIgcXVlIGNvbiBsb3MgcHJpbWVyb3MgZG9zIGVqZXMgc2UgZXhwbGljYSBhcHJveGltYWRhbWVudGUgdW4gNzAlIGRlIGxhIHZhcmlhbnphLCBsbyBjdWFsIHNlIGNvbnNpZGVyYSBzdWZpY2llbnRlLiBBbCBtaXNtbyB0aWVtcG8sIGVzdGUgY3JpdGVyaW8gc3ViamV0aXZvIGVzIGNvbXBhdGlibGUgY29uIGVsIHJlc3VsdGFkbyBhcnJvamFkbyBwb3IgZWwgU2NyZWUtcGxvdCwgc2kgc2Ugc2lndWUgZWwgY3JpdGVyaW8gZGUgS2Fpc2VyLCBkb25kZSBzZSBhcHJlY2lhIHF1ZSBsb3MgcHJpbWVyb3MgZG9zIGVqZXMgcHJlc2VudGFuIGF1dG92YWxvcmVzIG1heW9yZXMgYSAxLgoKRGVsIGJpcGxvdCBjb3JyZXBvbmRpZW50ZSBhIG1vc3RyYXIgbGFzIHZhcmlhYmxlcyB5IGxhcyBvYnNlcnZhY2lvbmVzIGVuIGxvcyBkb3MgcHJpbWVyb3MgZWplcyBkZWwgUENBLCBzZSBwdWVkZSBjb25jbHVpciBxdWUgaGF5IGRvcyB0aXBvcyBkZSB2YXJpYWJsZXMgcHJpbmNpcGFsZXM6IGxhcyBwcmltZXJhcyBzb24gcHJveHkgZGVsIHRhbWHDsW8gZGUgbGEgcHJvcGllZGFkOiBuw7ptZXJvIGRlIGFtYmllbnRlcywgaGFiaXRhY2lvbmVzLCBiYcOxb3MsIHN1cGVyZmljaWUgdG90YWwgeSBjdWJpZXJ0YS4gRWwgc2VndW5kbyBlamUgZXN0w6EgY29ycmVsYWNpb25hZG8gZnVlcnRlbWVudGUgY29uIGxhIGxhdGl0dWQsIHkgZWwgdGVyY2VybywgcXVlIG5vIHNlIG11ZXN0cmEgZW4gZ3LDoWZpY2FtZW50ZSBwZXJvIHPDrSBlbiBsYSB0YWJsYSBxdWUgc2UgbXVlc3RyYSBhbCBmaW5hbCBkZSBsYSBzZWNjacOzbiwgZG9uZGUgc2UgZW5jdWVudHJhbiBsb3MgbG9hZGluZ3MgbyBjYXJnYXMsIGVzdMOhIGZ1ZXJ0ZW1lbnRlIGNvcnJlbGFjaW9uYWRvIGNvbiBsYSBsb25naXR1ZC4gUG9yIGVzdGEgcmF6w7NuIHBvZHLDrWFtb3MgYWZpcm1hciBxdWUgZXhpc3RlIHVuIHNlZ3VuZG8gZ3J1cG8gZGUgZG9zIHZhcmlhYmxlcyBxdWUgc29uIGxhIHViaWNhY2nDs24gZ2VvZ3LDoWZpY2EuIEVsIHByZWNpbyBlc3TDoSBtw6FzIGNvcnJlbGFjaW9uYWRvICh5IGVuIGZvcm1hIGRpcmVjdGEpIGNvbiBlbCBwcmltZXIgZ3J1cG8gZGUgdmFyaWFibGVzIHF1ZSBjb24gZWwgc2VndW5kby4KCkVzdGUgcmVzdWx0YWRvIGVzIGzDs2dpY28gZGFkbyBxdWUgZWwgUENBIGRldGVjdGEgY29ycmVsYWNpb25lcywgYSBsbyBjdWFsIHN1YnlhY2UgbGEgaWRlYSBkZSBsaW5lYWxpZGFkLCB5IGVsIHByZWNpbyBlc3TDoSBsaWdhZG8sIGVuIHTDqXJtaW5vcyBnZW9ncsOhZmljb3MsIGEgbG9zIGRpc3RpbnRvcyBiYXJyaW9zLCBsbyBjdWFsIHNpZ3VlIHVuYSBsw7NnaWNhIGRpZmVyZW50ZSBhIGxhIGxpbmVhbCBjb24gbGEgbGF0aXR1ZCB5IGxhIGxvbmdpdHVkLgoKYGBge3IgcGNhfQp2YXJzX2N1YW50aV9wY2EgPC0gYygibGF0IiwgImxvbiIsICJyb29tcyIsICJiYXRocm9vbXMiLCAiYmVkcm9vbXMiLCAic3VyZmFjZV90b3RhbCIsICJzdXJmYWNlX2NvdmVyZWQiLCAicHJpY2UiKQoKZGF0b3NfY3VhbnRpX3BjYSA8LSBuYS5vbWl0KGRhdG9zWywgdmFyc19jdWFudGlfcGNhXSkKCmxpYnJhcnkoY29ycnBsb3QpCgptX2NvciA8LSBjb3IoZGF0b3NfY3VhbnRpX3BjYSkKY29ycnBsb3QobV9jb3IsCiAgICAgICAgIG1ldGhvZD0iY2lyY2xlIiwKICAgICAgICAgdHlwZSA9ICJ1cHBlciIsCiAgICAgICAgIGRpYWc9IEZBTFNFKSAKCmRhdG9zX2N1YW50aV9wY2Ffc3RkIDwtIGRhdGEuZnJhbWUoc2NhbGUoZGF0b3NfY3VhbnRpX3BjYSkpCgpwY2EgPC0gcHJjb21wKGRhdG9zX2N1YW50aV9wY2EsIHNjYWxlID0gVFJVRSkKCnByb3BfdmFyaWFuemEgPC0gcGNhJHNkZXZeMiAvIHN1bShwY2Ekc2Rldl4yKQpwcm9wX3ZhcmlhbnphX2FjdW0gPC0gY3Vtc3VtKHByb3BfdmFyaWFuemEpCnJvdW5kKHByb3BfdmFyaWFuemFfYWN1bSoxMDAsMikKCnNjcmVlcGxvdChwY2EsIHR5cGUgPSAibCIsIG5wY3MgPSA4KQphYmxpbmUoaCA9IDEsIGNvbD0icmVkIiwgbHR5PTUpCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQ9YygiYXV0b3ZhbG9yID0gMSIpLAogICAgICAgY29sPWMoInJlZCIpLCBsdHk9NSwgY2V4PTAuNikKCgpsaWJyYXJ5KGdnZm9ydGlmeSkKCmF1dG9wbG90KHBjYSwgCiAgICAgICAgIGRhdGEgPSBkYXRvc19jdWFudGlfcGNhLCAKICAgICAgICAgbG9hZGluZ3MgPSBUUlVFLCAKICAgICAgICAgbG9hZGluZ3MuY29sb3VyID0gJ2JsYWNrJywKICAgICAgICAgbG9hZGluZ3MubGFiZWwgPSBUUlVFLCAKICAgICAgICAgbG9hZGluZ3MubGFiZWwuc2l6ZSA9IDUpCgpsaWJyYXJ5KGthYmxlRXh0cmEpCnJvdW5kKHBjYSRyb3RhdGlvbiwyKSB8PiBrbml0cjo6a2FibGUoZm9ybWF0ID0gImh0bWwiKSB8PiAKICBrYWJsZV9zdHlsaW5nKCkKCmBgYAoKIyBQQ0Egcm9idXN0bwoKUGFyYSBlbCBQQ0Egcm9idXN0byBzZSBlbXBsZcOzIGVsIG3DqXRvZG8gZGVsIEVsaXBzb2lkZSBkZSBWb2x1bWVuIE3DrW5pbW8gKE1WRSkuCkxhIHByaW5jaXBhbCBkaWZlcmVuY2lhIHF1ZSBzZSBvYnNlcnZhIGVudHJlIGVsIFBDQSBjbMOhc2ljbyB5IGVsIHJvYnVzdG8gZXMgcXVlIGVuIGVzdGUgw7psdGltbywgbG9zIHB1bnRvcyBzZSBlbmN1ZW50cmFuIG1lbm9zIGFnbG9tZXJhZG9zIGVuIGVsIGJpcGxvdC4gUGFyYSBwb2RlciB2aXN1YWxpemFyIGVzdGEgZGlmZXJlbmNpYSBtw6FzIGNsYXJhbWVudGUsIHNlIHJlYWxpesOzIHVuIGNsdXN0ZXJpbmcgamVyw6FycXVpY28gY29uIGxvcyBtaXNtb3MgcGFyw6FtZXRyb3MgcXVlIGVsIG1vc3RyYWRvIGFudGVyaW9ybWVudGUsIHNvbG8gcXVlIHNlIHV0aWxpemFyb24gbGFzIHZhcmlhYmxlcyBkZWwgUENBIChzZSBpbmNsdXnDsyBjYW50aWRhZCBkZSBiYcOxb3MgeSBkZSBoYWJpdGFjaW9uZXMpIHkgc2UgZmlqw7MgY29tbyBjcml0ZXJpbyBkZSBjb3J0ZSBlbCBmb3JtYXIgZG9zIGdydXBvcy4gRXN0b3MgZG9zIGdydXBvcyBzb24gc2ltaWxhcmVzIG8gYW7DoWxvZ29zIGEgbG9zIGRvcyBncnVwb3MgbcOhcyBncmFuZGVzIHF1ZSBzZSBvYnNlcnZhYmFuIGVuIGVsIGJpcGxvdCBxdWUgbW9zdHJhYmEgbG9zIHJlc3VsdGFkb3MgZGVsIGNsdXN0ZXJpbmcgamVyw6FycXVpY28sIGVuIGxhIHNlY2Npw7NuIGNvcnJlc3BvbmRpZW50ZSAodXRpbGl6YW5kbyBlbCBtw6l0b2RvIGRlIGxpZ2FtaWVudG8gV2FyZCkuCgpFbiBsb3MgYmlwbG90cyBkZWwgUENBIGNsw6FzaWNvIHkgcm9idXN0byBzZSBwdWVkZSBvYnNlcnZhciBjb21vIGVzdGUgw7psdGltbyBzZXBhcmEgbWVqb3IgbG9zIHB1bnRvcyBkZSB1bm8gZGUgbG9zIGRvcyBncnVwb3MsIGFwYXJlY2VuIGNvbW8gbcOhcyAiZWxvbmdhZG9zIiBhIGxvIGxhcmdvIGRlbCBwcmltZXIgY29tcG9uZW50ZS4gQ29tbyBzZSBwdWVkZSBvYnNlcnZhciBlbiBlbCBncsOhZmljbyBxdWUgY29tcGFyYSBsYXMgdmFyaWFuemFzIGV4cGxpY2FkYXMgcG9yIGxvcyBjb21wb25lbnRlcyBwcmluY2lwYWxlcywgcGFyYSBlbCBQQ0EgY2zDoXNpY28gKG5vIHJvYnVzdG8pIHkgcGFyYSBlbCByb2J1c3RvIChNVkUpLCBlbXBsZWFyIGVsIG3DqXRvZG8gcm9idXN0byBpbXBsaWPDsyBwYXJhIGVzdGUgY2FzbyBkZSBlc3R1ZGlvLCB1bmEgcMOpcmRpZGEgZGUgbGEgcHJvcG9yY2nDs24gZGUgbGEgdmFyaWFuemEgZXhwbGljYWRhIHBvciBsb3MgcHJpbWVyb3MgZWplcy4KCkNvbW8gc2UgcHVlZGUgYXByZWNpYXIgZW4gbG9zIGJpcGxvdHMsIGVuIGVsIFBDQSByb2J1c3RvIGxhcyB2YXJpYWJsZXMgdGFtYmnDqW4gYXBhcmVjZW4gbcOhcyBzZXBhcmFkYXMgZW50cmUgc8OtLCB5IGVzdG8gc2UgcHVlZGUgYXByZWNpYXIgZW4gbG9zIGxvYWRpbmdzLCBkb25kZSB2ZW1vcyBxdWUgbGFzIGNvcnJlbGFjaW9uZXMgZGUgbGFzIHZhcmlhYmxlcyBvcmlnaW5hbGVzIGNvbiBsb3MgZWplcyBubyBzb24gdGFuIGV4dHJlbWFzIChtdXkgcHLDs3hpbWFzIGEgMSBvIC0xLCBvIGEgMCkgeSBlc3TDoW4gcmVwYXJ0aWRhcyBtw6FzICJlcXVpdGF0aXZhbWVudGUiIChzZSBvYnNlcnZhbiBjb2VmaWNpZW50ZXMgZGUgY29ycmVsYWNpw7NuIGludGVybWVkaW9zKS4gT3RybyBwdW50byBwYXJhIGRlc3RhY2FyIGVzIHF1ZSBsYSBsYXRpdHVkIHkgbGEgbG9uZ2l0dWQgcGFyZWNlbiBtw6FzIG9ydG9nb25hbGVzIGVuIGVsIGJpcGxvdCBkZWwgUENBIGNsw6FzaWNvIHF1ZSBlbiBlbCBQQ0Egcm9idXN0bywgZG9uZGUgc2UgZW5jdWVudHJhbiBwcsOhY3RpY2FtZW50ZSBhbGluZWFkYXMuCgpgYGB7ciBwY2Ffcm9idXN0b30Kc2V0LnNlZWQoODkzKQpwY2FfbXZlIDwtcHJpbmNvbXAoZGF0b3NfY3VhbnRpX3BjYSwgCiAgICAgICAgICAgICAgICAgICBjb3I9VFJVRSwgCiAgICAgICAgICAgICAgICAgICBzY29yZXM9VFJVRSwKICAgICAgICAgICAgICAgICAgIGNvdm1hdD1NQVNTOjpjb3YubXZlKGRhdG9zX2N1YW50aV9wY2EpKSAjZW1wbGVhbW9zIE1WRSBjb24gZGF0b3MgZXN0YW5kYXJpemFkb3MKCnNjcmVlcGxvdChwY2FfbXZlLCB0eXBlID0gImwiLCBucGNzID0gNykKYWJsaW5lKGggPSAxLCBjb2w9InJlZCIsIGx0eT01KQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kPWMoIkF1dG92YWxvciA9IDEiKSwKY29sPWMoInJlZCIpLCBsdHk9NSwgY2V4PTAuNikKCgphdXRvcGxvdChwY2FfbXZlLCAKICAgICAgICAgZGF0YSA9IGRhdG9zX2N1YW50aV9wY2Ffc3RkLCAKICAgICAgICAgbG9hZGluZ3MgPSBUUlVFLCAKICAgICAgICAgbG9hZGluZ3MuY29sb3VyID0gJ2JsYWNrJywKICAgICAgICAgbG9hZGluZ3MubGFiZWwgPSBUUlVFLCAKICAgICAgICAgbG9hZGluZ3MubGFiZWwuc2l6ZSA9IDUpCgptYXRfZGlzdF9wY2EgPC0gZGlzdCh4ID0gZGF0b3NfY3VhbnRpX3BjYV9zdGQsIG1ldGhvZCA9ICJldWNsaWRlYW4iKSAKaGNfd2FyZF9wY2EgPC0gaGNsdXN0KGQgPSBtYXRfZGlzdF9wY2EsIG1ldGhvZCA9ICJ3YXJkLkQyIikKY2x1c3RlcnNfcGNhID0gY3V0cmVlKGhjX3dhcmRfcGNhLCBrID0gMikKCnRoZW1lIDwtIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMiwgZmFjZT0iYm9sZC5pdGFsaWMiLAogICAgICAgICAgICAgICBoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTEwLCBmYWNlPSJib2xkIiwgY29sb3VyPSdibGFjaycpLAogICAgICAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCwgZmFjZT0iYm9sZCIpLAogICAgICAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZT0iYm9sZCIpKQoKbGlicmFyeShnZ2JpcGxvdCkKI0NvbXBhcmFjacOzbiBkZSBiaXBsb3RzCiNCaXBsb3QgZGVsIFBDQSBjbMOhc2ljbwpnZ2JpcGxvdChwY2EsIG9icy5zY2FsZT0wLjEgLHZhci5zY2FsZT0xLGFscGhhPTAuNQogICAgICAgICAsZ3JvdXBzPWZhY3RvcihjbHVzdGVyc19wY2EpKSArIHRoZW1lCiNCaXBsb3QgZGVsIFBDQSByb2J1c3RvIChNVkUpCmdnYmlwbG90KHBjYV9tdmUsIG9icy5zY2FsZT0wLjEgLHZhci5zY2FsZT0xLGFscGhhPTAuNQogICAgICAgICAsZ3JvdXBzPWZhY3RvcihjbHVzdGVyc19wY2EpKSArIHRoZW1lCgpsaWJyYXJ5KGdncHVicikKCnBhcihtZnJvdz1jKDIsMSkpCnAxIDwtZnZpel9laWcocGNhX212ZSwgbmNwID01LCBhZGRsYWJlbHMgPSBUUlVFLCBtYWluPSJNVkUiKQpwMjwtIGZ2aXpfZWlnKHBjYSwgbmNwID01LCBhZGRsYWJlbHMgPSBUUlVFLCBtYWluPSJObyByb2J1c3RvIikKZ2dhcnJhbmdlKHAxLHAyLCBucm93ID0gMSwgbmNvbCA9IDIpCgpwY2FfbXZlJGxvYWRpbmdzCmBgYAoKIyBBbsOhbGlzaXMgZGUgY29ycmVzcG9uZGVuY2lhcyBzaW1wbGUKCkEgZmluIGRlIHJlYWxpemFyIGVsIGFuw6FsaXNpcyBkZSBjb3JyZXNwb25kZW5jaWFzLCBzZSBkaXNjcmV0aXphcm9uIGxhcyBzaWd1aWVudGUgdmFyaWFibGVzIGN1YW50aXRhdGl2YXM6CgpQcmVjaW8sIExhdGl0dWQsIExvbmdpdHVkLCBTdXBlcmZpY2llIHRvdGFsLCBTdXBlcmZpY2llIGN1YmllcnRhIHkgTsO6bWVybyBkZSBhbWJpZW50ZXMuCgpFbiBjYWRhIGNhc28gc2UgZWxpZ2llcm9uIHRyZXMgY2F0ZWdvcsOtYXMsIHkgbG9zIGRhdG9zIHNlIHJlcGFydGllcm9uIGVuIGZvcm1hIHVuaWZvcm1lIGVuIGNhZGEgdW5hIGRlIGxhcyBjYXRlZ29yw61hcy4gRXMgZGVjaXIsIGNhZGEgY2F0ZWdvcsOtYSBjb250aWVuZSAxLzMgZGUgbG9zIGRhdG9zLgoKUGFyYSBlbCBhbsOhbGlzaXMgZGUgY29ycmVwb25kZW5jaWFzIHNpbXBsZSBzZSBlbXBsZWFyb24gbGFzIHZhcmlhYmxlcywgYXPDrSBkaXNjcmV0aXphZGFzOiBQcmVjaW8geSBTdXBlcmZpY2llIHRvdGFsLgpDb21vIGVuIGxhIHRhYmxhIGRlIGZyZWN1ZW5jaWFzIGVzcGVyYWRhcyBjb3JyZXNwb25kaWVudGUgYSBsYSB0YWJsYSBkZSBjb250aW5nZW5jaWEgc2UgZGV0ZWN0YXJvbiBjZWxkYXMgY29uIGZyZWN1ZW5jaWFzIG1lbm9yZXMgYSA1LCBzZSBmdXNpb25hcm9uIGxhcyBjYXRlZ29yw61hcyBtZWRpYSB5IGFsdGEgZGUgYW1iYXMgdmFyaWFibGVzLCBwYXJhIHBvZGVyIHJlYWxpemFyIGVsIHRlc3QgZGUgaW5kZXBlbmRlbmNpYSBkZSBDaGktY3VhZHJhZG8uIAoKQ29uIGxhIHRhYmxhIGFzw60gY29uc3RydcOtZGEsIGNvbiBkb3MgdmFyaWFibGVzIGFob3JhIGRpY290w7NtaWNhcywgc2UgcmVhbGl6w7MgZWwgdGVzdCBkZSBpbmRlcGVuZGVuY2lhIGRlIENoaS1jdWFkYXJhZG8uIEVsIHRlc3QgcmVzdWx0YSBzaWduaWZpY2F0aXZvIChwPDAuMDUpLCBlcyBkZWNpciBxdWUgbGFzIHZhcmlhYmxlcyBzb24gZGVwZW5kaWVudGVzLiBFc3RlIHJlc3VsdGFkbyBubyBlcyBzb3JwcmVuZGVudGUgc2kgc2Ugb2JzZXJ2YW4gbG9zIGdyw6FmaWNvcyBkZSBQcmVjaW8gc2Vnw7puIFN1cGVyZmljaWUgdG90YWwsIG8gc2kgc2Ugb2JzZXJ2YW4gbG9zIGdyw6FmaWNvcyBkZSBwZXJmaWxlcyAocGFyYSBsb3MgZGF0b3Mgc3ViZGl2aWRpZG9zIGVuIHRyZXMgY2F0ZWdvcsOtYXMgcG9yIHZhcmlhYmxlKS4KClB1ZXN0byBxdWUgZWwgdGVzdCBkZSBpbmRlcGVuZGVuY2lhIGZ1ZSBzaWduaWZpY2F0aXZvLCBzZSBhYnJlIHBhc28gYWwgQW7DoWxpc2lzIGRlIENvcnJlc3BvbmRlbmNpYXMgU2ltcGxlIChDQSksIHBhcmEgZWwgY3VhbCBlbXBsZWFtb3MgbG9zIGRhdG9zIHN1YmRpdmlkaWRvcyBlbiB0cmVzIGNhdGVnb3LDrWFzIHBvciB2YXJpYWJsZS4gQ29tbyBlcyBkZSBlc3BlcmFyLCBzZSBwdWVkZSBhcHJlY2lhciBxdWUgZWwgcHJpbWVyIGNvbXBvbmVudGUgYWJzb3JiZSBsYSBtYXlvciBwYXJ0ZSBkZSBsYSBpbmVyY2lhIHRvdGFsLCB5IGRlIGhlY2hvLCBhIGxvIGxhcmdvIGRlIGVzdGUgZWplIHZlbW9zIHF1ZSBzZSB2YW4gb3JkZW5hbmRvIGxhcyBvYnNlcnZhY2lvbmVzIGNvbiBwcmVjaW8gYmFqbyB5IHN1cGVyZmljaWUgdG90YWwgYmFqYSBlbiB1biBleHRyZW1vLCBlbiBlbCBtZWRpbyBsYXMgZGUgcHJlY2lvIG1lZGlvIHkgc3VwZXJmaWNpZSB0b3RhbCBtZWRpYSwgeSBlbiBleHRyZW1vIG9wdWVzdG8gbG9zIHZhbG9yZXMgYWx0b3MgZGUgYW1iYXMgdmFyaWFibGVzLgoKU2kgc2Ugb2JzZXJ2YSBsYSBjb250cmlidWNpw7NuIGRlIGZpbGFzIHkgZGUgY29sdW1uYXMgYSBsYSBpbmVyY2lhIHRvdGFsLCBwb2RlbW9zIGNvbmNsdWlyIHF1ZSBsYXMgY2F0ZWdvcsOtYXMgcXVlIG3DoXMgY29udHJpYnV5ZW4gYSBsYSBmYWx0YSBkZSBpbmRlcGVuZGVuY2lhIHNvbiBQcmVjaW8gQWx0byB5IFN1cGVyZmljaWUgdG90YWwgQWx0YS4KCmBgYHtyIGNhfQpsaWJyYXJ5KEZhY3RvTWluZVIpCgpkYXRvc19kaXNjcmV0aXphZG9zIDwtIGRhdGEuZnJhbWUoCnJhbmdvX3ByZWNpbyA8LSBjdXQoZGF0b3MkcHJpY2UsIGJyZWFrcyA9IDMsIGxhYmVscyA9IGMoIkJham8iLCAiTWVkaW8iLCAiQWx0byIpKSwKcmFuZ29fbG9uIDwtIGN1dChkYXRvcyRsb24sIGJyZWFrcyA9IDMsIGxhYmVscyA9IGMoIk9lc3RlIiwgIkNlbnRybyIsICJFc3RlIikpLApyYW5nb19sYXQgPC0gY3V0KGRhdG9zJGxhdCwgYnJlYWtzID0gMywgbGFiZWxzID0gYygiU3VyIiwgIkNlbnRybyIsICJOb3J0ZSIpKSwKcmFuZ29fc3VwX2N1YiA8LSBjdXQoZGF0b3Mkc3VyZmFjZV9jb3ZlcmVkLCBicmVha3MgPSAzLCBsYWJlbHMgPSBjKCJCYWphIiwgIk1lZGlhIiwgIkFsdGEiKSksCnJhbmdvX3N1cF90b3QgPC0gY3V0KGRhdG9zJHN1cmZhY2VfdG90YWwsIGJyZWFrcyA9IDMsIGxhYmVscyA9IGMoIkJhamEiLCAiTWVkaWEiLCAiQWx0YSIpKSwKcmFuZ29fY2FudF9hbWIgPC0gY3V0KGRhdG9zJHJvb21zLCBicmVha3MgPSAzLCBsYWJlbHMgPSBjKCJCYWphIiwgIk1lZGlhIiwgIkFsdGEiKSkKKQpjb2xuYW1lcyhkYXRvc19kaXNjcmV0aXphZG9zKSA9IGMoIlByZWNpbyIsIkxvbiIsICJMYXQiLCAiU3VwX2N1YmllcnRhIiwgIlN1cF90b3RhbCIsICJDYW50X2FtYiIpCmRhdG9zX2Rpc2NyZXRpemFkb3MgPSBuYS5vbWl0KGRhdG9zX2Rpc2NyZXRpemFkb3MpCgp0YWJsYV9mcmVjdWVuY2lhcyA9IHRhYmxlKGRhdG9zX2Rpc2NyZXRpemFkb3MkUHJlY2lvLCBkYXRvc19kaXNjcmV0aXphZG9zJFN1cF90b3RhbCkKCgpkaW1uYW1lcyh0YWJsYV9mcmVjdWVuY2lhcyk8LWxpc3QoUHJlY2lvPWMoIkJham8iLCJNZWRpbyIsIkFsdG8iKSxTdXBfdG90YWw9YygiQmFqYSIsIk1lZGlhIiwiQWx0YSIpKQojcm93bmFtZXModGFibGFfZnJlY3VlbmNpYXMpIDwtICBwYXN0ZSgiUHJlY2lvXyIsIHJvd25hbWVzKHRhYmxhX2ZyZWN1ZW5jaWFzKSwgc2VwID0gIiIpCiNjb2xuYW1lcyh0YWJsYV9mcmVjdWVuY2lhcykgPC0gIHBhc3RlKCJTdXBfdG90YWxfIiwgY29sbmFtZXModGFibGFfZnJlY3VlbmNpYXMpLCBzZXAgPSAiIikKZGZfZnJlY3VlbmNpYXMgPSBhcy5kYXRhLmZyYW1lLnRhYmxlKHRhYmxhX2ZyZWN1ZW5jaWFzKQpkZl9mcmVjdWVuY2lhc19leHAgPC0gZGZfZnJlY3VlbmNpYXNbcmVwKDE6bnJvdyhkZl9mcmVjdWVuY2lhcyksIGRmX2ZyZWN1ZW5jaWFzWywzXSksLTNdCgoKY2F0ZWdvcmlhX3ByZWNpb19tZWRpb19hbHRvIDwtIGlmZWxzZShkZl9mcmVjdWVuY2lhc19leHAkUHJlY2lvICVpbiUgYygiTWVkaW8iLCAiQWx0byIpLCAiTWVkaW9BbHRvIiwgIkJham8iKQpjYXRlZ29yaWFfc3VwX3RvdGFsX21lZGlhX2FsdGEgPC0gaWZlbHNlKGRmX2ZyZWN1ZW5jaWFzX2V4cCRTdXBfdG90YWwgJWluJSBjKCJNZWRpYSIsICJBbHRhIiksICJNZWRpYUFsdGEiLCAiQmFqYSIpCnRhYmxhX2ZyZWN1ZW5jaWFzX2Z1c2lvbmFkYXMgPSB0YWJsZShjYXRlZ29yaWFfcHJlY2lvX21lZGlvX2FsdG8sY2F0ZWdvcmlhX3N1cF90b3RhbF9tZWRpYV9hbHRhKQpkaW1uYW1lcyh0YWJsYV9mcmVjdWVuY2lhc19mdXNpb25hZGFzKTwtbGlzdChQcmVjaW89YygiQmFqbyIsIk1lZGlvLUFsdG8iKSxTdXBfdG90YWw9YygiQmFqYSIsIk1lZGlhLUFsdGEiKSkKCnRhYmxhX2ZyZWN1ZW5jaWFzCmNoaXNxLnRlc3QodGFibGFfZnJlY3VlbmNpYXMpJGV4cGVjdGVkCgp0YWJsYV9mcmVjdWVuY2lhc19mdXNpb25hZGFzCgpjaGlzcS50ZXN0KHRhYmxhX2ZyZWN1ZW5jaWFzX2Z1c2lvbmFkYXMpCmxpYnJhcnkoZ3Bsb3RzKQpiYWxsb29ucGxvdCh0KGFzLnRhYmxlKGFzLm1hdHJpeCh0YWJsYV9mcmVjdWVuY2lhc19mdXNpb25hZGFzKSkpLCBtYWluID0iUHJlY2lvIHNlZ8O6biBTdXBlcmZpY2llIHRvdGFsIiwgeGxhYiA9IiIsIHlsYWI9IiIsCiAgICAgICAgICAgIGxhYmVsID0gRkFMU0UsIHNob3cubWFyZ2lucyA9IEZBTFNFKQoKcGFyKGJnPSJsaWdodGN5YW4iKQpiYXJwbG90KHRhYmxhX2ZyZWN1ZW5jaWFzX2Z1c2lvbmFkYXMsYmVzaWRlPVRSVUUsY29sPSBjKCJhcXVhbWFyaW5lMyIsInRhbjEiKSx5bGltPWMoMCwyODApLHlsYWI9IkNhbnRpZGFkIGRlIHByb3BpZWRhZGVzIikKbXRleHQoIlByZWNpbyBzZWfDum4gbGEgU3VwZXJmaWNpZSB0b3RhbCIsY2V4PTEsbGluZT0xKQpsZWdlbmQoInRvcHJpZ2h0IixjZXg9MC44LHRpdGxlPSJQcmVjaW8iLGMoIkJham8iLCJNZWRpby1BbHRvIiksIGZpbGw9YygiYXF1YW1hcmluZTMiLCJ0YW4xIiksaG9yaXo9RiwgYm94Lmx0eSA9IDApCgpwcm9wLnRhYmxlKHRhYmxhX2ZyZWN1ZW5jaWFzX2Z1c2lvbmFkYXMpCgpnZ3Bsb3QoZGF0YT1kZl9mcmVjdWVuY2lhc19leHAsIGFlcyh4ID0gUHJlY2lvLCBmaWxsPSBTdXBfdG90YWwpKStnZW9tX2Jhcihwb3NpdGlvbj0nZmlsbCcsIGFscGhhPTAuOSkrCiAgbGFicyh0aXRsZSA9ICdEaXN0cmlidWNpw7NuIGRlIGxhIGNhdGVnb3LDrWEgc3VwZXJmaWNpZSB0b3RhbCBzZWfDum4gcHJlY2lvJywKICAgICAgICAgICAgICB5ID0gJ0ZyZWN1ZW5jaWEgZGUgcHJvcGllZGFkZXMnLCB4ID0gJ1ByZWNpbycpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChuYW1lPSdTdXBlcmZpY2llIHRvdGFsJykgCgpwcmVjaW9fc3VwLmFjID0gQ0EodGFibGFfZnJlY3VlbmNpYXMsZ3JhcGg9RkFMU0UpCgojIENvbnRyaWJ1Y2nDs24gZGUgZmlsYXMgeSBjb2x1bW5hcyBhbCBlamUgMQpmdml6X2NvbnRyaWIocHJlY2lvX3N1cC5hYyxjaG9pY2U9InJvdyIsYXhlcz0xLCBmaWxsPSJyb3lhbGJsdWUiLGNvbG9yID0iYmxhY2siKSArIGxhYnModGl0bGUgPSAnQ29udHJpYnVjacOzbiBkZSBmaWxhcycsICB4ID0gJ1ByZWNpbycsIHkgPSAnQ29udHJpYnVjacOzbiAoJSknKQpmdml6X2NvbnRyaWIocHJlY2lvX3N1cC5hYyxjaG9pY2U9ImNvbCIsYXhlcz0xLCBmaWxsPSJyb3lhbGJsdWUiLGNvbG9yID0iYmxhY2siKSArIGxhYnModGl0bGUgPSAnQ29udHJpYnVjacOzbiBkZSBjb2x1bW5hcycsICB4ID0gJ1N1cGVyZmljaWUnLCB5ID0gJ0NvbnRyaWJ1Y2nDs24gKCUpJykKZnZpel9jYV9iaXBsb3QoIHByZWNpb19zdXAuYWMgLCByZXBlbCAgPVRSVUUsIGNvbC5yb3c9InJveWFsYmx1ZSIsY29sLmNvbD0iaW5kaWFucmVkIikgKyBsYWJzKHRpdGxlPSdCaXBsb3QgQW7DoWxpc2lzIGRlIGNvcnJlc3BvbmRlbmNpYXMgU2ltcGxlJykKYGBgCgojIEFuw6FsaXNpcyBkZSBjb3JyZXNwb25kZW5jaWFzIG3Dumx0aXBsZQpQYXJhIGVsIGFuw6FsaXNpcyBkZSBjb3JyZXNwb25kZW5jaWFzIG3Dumx0aXBsZSAoTUNBKSwgc2UgZW1wbGVhcm9uIHRvZGFzIGxhcyB2YXJpYWJsZXMgcXVlIHNlIGhhYsOtYW4gY2F0ZWdvcml6YWRvIGVuIGVsIHB1bnRvIGFudGVyaW9yLgoKQ29tbyBzZSBwdWVkZSBhcHJlY2lhciBlbiBlbCBTY3JlZS1wbG90IGNvcnJlc3BvbmRpZW50ZSwgZWwgcHJpbWVyIGVqZSBlcyBlbCBxdWUgbcOhcyBhYnNvcmJlIGRlIGxhIGluZXJjaWEgdG90YWwsIHkgbHVlZ28gbGEgY29udHJpYnVjacOzbiBlcyBwYXJlamEuIEVsZWdpbW9zIGRvcyBlamVzIHBhcmEgcG9kZXIgdmlzdWFsaXphciBsb3MgcHVudG9zIGVuIGRvcyBkaW1lbnNpb25lcy4gRW1wbGVhciBtw6FzIGRpbWVuc2lvbmVzLCBvIHNlYSBtb3N0cmFyIGxvcyBiaXBsb3RzIHBhcmEgbGFzIGRpc3RpbnRhcyBjb21iaW5hY2lvbmVzIGRlIGVqZXMgY29tcGxpY2Fyw61hIGVsIGFuw6FsaXNpcyB5IGxhIGludGVycHJldGFjacOzbi4KCkVuIGVsIGJpcGxvdCBkZSBsb3MgZG9zIHByaW1lcm9zIGVqZXMgc2UgcHVlZGUgYXByZWNpYXIgY8OzbW8gbGFzIG9ic2VydmFjaW9uZXMgcXVlIHRpZW5lbiBwcmVjaW8gYWx0byB0YW1iacOpbiB0aWVuZW4gc3VwZXJmaWNpZSB0b3RhbCB5IGN1YmllcnRhIGFsdGEsIHkgZXN0YXMgb2JzZXJ2YWNpb25lcyBzb24gY29tcGFyYXRpdmFtZW50ZSBwb2NhcywgZW4gY29tcGFyYWNpw7NuIGNvbiBsYXMgcmVzdGFudGVzLiBEZSBoZWNobyBsYSBtYXlvciBwYXJ0ZSBkZSBsYXMgb2JzZXJ2YWNpb25lcyBzZSBjb25jZW50cmFuIGVuIGxvcyB2YWxvcmVzIGJham9zIGRlIGxhcyB2YXJpYWJsZXMsIHkgbGEgbnViZSBkZSBwdW50b3Mgc2UgdmEgZWxvbmdhbmRvIHkgZGlzcGVyc2FuZG8gaGFjaWEgbG9zIHZhbG9yZXMgbWVkaW9zIGRlIHByZWNpbyB5IGRlIHN1cGVyZmljaWUgdG90YWwgeSBjdWJpZXJ0YS4KCkFsZ3VuYXMgZGUgbGFzIG9ic2VydmFjaW9uZXMgcXVlIG3DoXMgYXBvcnRhbiBhIGxhIGluZXJjaWEgdG90YWwsIHF1ZSBzZSBzZXBhcmFuIGRlbCByZXN0byB5IGVzdMOhbiBhc29jaWFkYXMgYSB2YWxvcmVzIGFsdG9zIGRlIGxhcyB2YXJpYWJsZXMsIHlhIGhhYsOtYW4gc2lkbyBpZGVudGlmaWNhZGFzIGVuIGVsIGNsdXN0ZXJpbmcgamVyw6FycXVpY28gcmVhbGl6YWRvIGNvbiBlbCBtw6l0b2RvIGRlIGxpZ2FtaWVudG8gcHJvbWVkaW8uCgpgYGB7ciBtY2F9CmRhdG9zLm1jYSA8LSBNQ0EoZGF0b3NfZGlzY3JldGl6YWRvcywgZ3JhcGggPSBGQUxTRSkKI0xvYWRpbmdzCmRhdG9zLm1jYSR2YXIkY29vcmQKZnZpel9zY3JlZXBsb3QoZGF0b3MubWNhLCBhZGRsYWJlbHMgPSBUUlVFKQpmdml6X21jYV9iaXBsb3QoIGRhdG9zLm1jYSAsIHJlcGVsICA9VFJVRSwgY29sLmluZD0iY29zMiIsaW52aXNpYmxlPSJxdWFsaSIpCmBgYA==